Edit on GitHub

utils.databases.simbad

Getting data from SIMBAD astronomical database.

  1"""
  2Getting data from [SIMBAD](https://simbad.u-strasbg.fr/simbad/)
  3astronomical database.
  4"""
  5
  6from astroquery.simbad import Simbad
  7from astroquery import __version__ as astroqueryVersion
  8import re
  9
 10from typing import Optional, Any
 11
 12from ..logs.log import logger
 13from ..databases import tap
 14
 15
 16def findIdentificatorFromAnotherCatalogue(
 17    starName: str,
 18    otherIDname: str,
 19    otherIDversion: Optional[str] = None,
 20    withoutIDprefix: bool = True
 21) -> Optional[str]:
 22    """
 23    Finds object identificator from a particular catalogue. SIMBAD returns
 24    a list of identificators prepended with the catalogue name, and this
 25    function finds the identificator for the specified catalogue and returns
 26    just the identificator. In addition to that, some catalogues have versions,
 27    so it is possible to specify which one if of interest.
 28
 29    For instance, if you need to find the identificator for `TWA 20` star
 30    in `Gaia` catalog version `DR3`, then SIMBAD will return
 31    `Gaia DR3 6132146982868270976` value for it, out of which this function
 32    will extract `6132146982868270976` as the result.
 33
 34    Example:
 35
 36    ``` py
 37    from phab.utils.databases import simbad
 38
 39    otherID = simbad.findIdentificatorFromAnotherCatalogue(
 40        "TWA 20",
 41        "gaia",
 42        "dr3"
 43    )
 44    print(otherID)
 45    ```
 46    """
 47    otherID = None
 48
 49    otherIDs = Simbad.query_objectids(starName)
 50    if otherIDs is None:
 51        logger.warning(
 52            " ".join((
 53                "SIMBAD database doesn't have information",
 54                f"about [{starName}]"
 55            ))
 56        )
 57    else:
 58        logger.debug(f"Checking SIMBAD IDs for [{starName}]:")
 59
 60        # before astroquery version 0.4.8 this table had
 61        # an upper-cased `ID` column key, but starting
 62        # with version 0.4.8 it is now lower-cased `id`
 63        #
 64        # https://github.com/astropy/astropy/issues/17695
 65        idColumnKey: str = "ID"
 66        # or compare `astroquery.__version__` with `0.4.7`
 67        if idColumnKey not in otherIDs.colnames:
 68            logger.debug(
 69                " ".join((
 70                    "There is no upper-cased [ID] key",
 71                    "in the resulting table, will try",
 72                    "with lower-cased [id] key"
 73                ))
 74            )
 75            idColumnKey = idColumnKey.lower()  # "id"
 76            if idColumnKey not in otherIDs.colnames:
 77                errorMsg = " ".join((
 78                    "SIMBAD results table has neither [ID]",
 79                    "nor [id] column"
 80                ))
 81                logger.error(errorMsg)
 82                if len(otherIDs.colnames) > 0:
 83                    logger.debug(
 84                        " ".join((
 85                            "Here are all the columns/keys",
 86                            "in this table:",
 87                            ", ".join(otherIDs.colnames)
 88                        ))
 89                    )
 90                else:
 91                    logger.debug(
 92                        " ".join((
 93                            "There are no columns/keys",
 94                            "in this table at all"
 95                        ))
 96                    )
 97                raise KeyError(errorMsg)
 98
 99        for oid in otherIDs:
100            idCandidate: str = oid[idColumnKey]
101            logger.debug(f"- {idCandidate}")
102            if otherIDname.lower() in idCandidate.lower():
103                idToLookFor = (
104                    f"{otherIDname} {otherIDversion}"
105                    if otherIDversion else otherIDname
106                )
107                if idToLookFor.lower() in idCandidate.lower():
108                    if withoutIDprefix:
109                        prefixRE = re.compile(
110                            rf"{idToLookFor}\s?",
111                            re.IGNORECASE
112                        )
113                        otherID = prefixRE.sub("", idCandidate)
114                    else:
115                        otherID = idCandidate
116                    break
117    return otherID
118
119
120def getObjectID(starName: str) -> Optional[int]:
121    """
122    Finds object identificator for
123    [SIMBAD tables](http://simbad.cds.unistra.fr/simbad/tap/tapsearch.html).
124    It is stored in the `oid` field of the `basic` table.
125
126    The discovery process is to compare all the known object identificators
127    with the `main_id` field value (*also from the `basic` table*). It is
128    not clear, how exactly SIMBAD maintainers choose the main ID for an object,
129    so one has to iterate through all the identificators known to SIMBAD.
130
131    Example:
132
133    ``` py
134    from phab.utils.databases import simbad
135    from typing import Optional
136
137    oid: Optional[int] = None
138    try:
139        oid = simbad.getObjectID("A2 146")
140    except KeyError as ex:
141        print(f"Something wrong with querying SIMBAD. {repr(ex)}")
142    if oid is not None:
143        print(f"Object ID: {oid}")
144    else:
145        print("No results")
146    ```
147    """
148    oid: Optional[int] = None
149
150    # check if this name is already the main ID
151    logger.debug(f"Checking whether [{starName}] is already the main ID")
152    rez = tap.queryService(
153        tap.getServiceEndpoint("simbad"),
154        " ".join((
155            "SELECT oid",
156            "FROM basic",
157            f"WHERE main_id = '{starName}'"
158        ))
159    )
160    if rez:
161        oid = rez[0]["oid"]
162        logger.debug(
163            " ".join((
164                f"- yes, that is already the main ID,",
165                "no need to iterate all the identificators.",
166                f"SIMBAD object ID is: {oid}"
167            ))
168        )
169    else:
170        logger.debug(
171            " ".join((
172                "- no, that is not the main ID, will have to iterate",
173                "all the other identificators"
174            ))
175        )
176        ids = Simbad.query_objectids(starName)
177        if ids is None:
178            logger.warning(
179                " ".join((
180                    "SIMBAD database doesn't have information",
181                    f"about [{starName}]"
182                ))
183            )
184        else:
185            logger.debug(f"Checking SIMBAD IDs for [{starName}]:")
186
187            # before astroquery version 0.4.8 this table had
188            # an upper-cased `ID` column key, but starting
189            # with version 0.4.8 it is now lower-cased `id`
190            #
191            # https://github.com/astropy/astropy/issues/17695
192            idColumnKey: str = "ID"
193            # or compare `astroquery.__version__` with `0.4.7`
194            if idColumnKey not in ids.colnames:
195                logger.debug(
196                    " ".join((
197                        "There is no upper-cased [ID] key",
198                        "in the resulting table, will try",
199                        "with lower-cased [id] key"
200                    ))
201                )
202                idColumnKey = idColumnKey.lower()  # "id"
203                if idColumnKey not in ids.colnames:
204                    errorMsg = " ".join((
205                        "SIMBAD results table has neither [ID]",
206                        "nor [id] column"
207                    ))
208                    logger.error(errorMsg)
209                    if len(ids.colnames) > 0:
210                        logger.debug(
211                            " ".join((
212                                "Here are all the columns/keys",
213                                "in this table:",
214                                ", ".join(ids.colnames)
215                            ))
216                        )
217                    else:
218                        logger.debug(
219                            " ".join((
220                                "There are no columns/keys",
221                                "in this table at all"
222                            ))
223                        )
224                    raise KeyError(errorMsg)
225
226            for id in ids:
227                idValue: str = id[idColumnKey]
228                logger.debug(f"- {idValue}")
229
230                if idValue == starName:
231                    logger.debug(
232                        f"...the [{idValue}] has already been tested, skipping"
233                    )
234                    continue
235                rez = tap.queryService(
236                    tap.getServiceEndpoint("simbad"),
237                    " ".join((
238                        "SELECT oid",
239                        "FROM basic",
240                        f"WHERE main_id = '{idValue}'"
241                    ))
242                )
243                if rez:
244                    oid = rez[0]["oid"]
245                    logger.debug(
246                        " ".join((
247                            f"The [{idValue}] is the main ID for",
248                            f"[{starName}], SIMBAD object ID is: {oid}"
249                        ))
250                    )
251                    break
252    return oid
253
254
255def getStellarParameter(
256    starName: str,
257    table: str,
258    param: str
259) -> Optional[tuple[Any, str]]:
260    """
261    A convenience function for querying SIMBAD for a stellar parameter:
262
263    1. Finds SIMBAD's object ID by the star name
264    (*with `utils.databases.simbad.getObjectID`*);
265    2. Queries for a stellar parameter by that object ID
266    (*with `utils.databases.tap.getStellarParameterFromSimbadByObjectID`*).
267
268    Example:
269
270    ``` py
271    from phab.utils.databases import simbad
272
273    starName = "PPM 725297"
274    param = "period"
275    rez = simbad.getStellarParameter(
276        starName,
277        "mesVar",
278        param
279    )
280    if rez:
281        val = rez[0]
282        ref = rez[1]
283        print(f"Value: {val}, reference: {ref}")
284    else:
285        print(f"SIMBAD doesn't have data about [{param}] parameter of [{starName}] object")
286    ```
287    """
288    rez: Optional[tuple[Any, str]] = None
289
290    oid = getObjectID(starName)
291    if oid:
292        # logger.debug(
293        #     f"Found the following object ID for [{starName}]: {oid}"
294        # )
295        rez = tap.getStellarParameterFromSimbadByObjectID(
296            oid,
297            table,
298            param
299        )
300
301    return rez
def findIdentificatorFromAnotherCatalogue( starName: str, otherIDname: str, otherIDversion: Optional[str] = None, withoutIDprefix: bool = True) -> Optional[str]:
 17def findIdentificatorFromAnotherCatalogue(
 18    starName: str,
 19    otherIDname: str,
 20    otherIDversion: Optional[str] = None,
 21    withoutIDprefix: bool = True
 22) -> Optional[str]:
 23    """
 24    Finds object identificator from a particular catalogue. SIMBAD returns
 25    a list of identificators prepended with the catalogue name, and this
 26    function finds the identificator for the specified catalogue and returns
 27    just the identificator. In addition to that, some catalogues have versions,
 28    so it is possible to specify which one if of interest.
 29
 30    For instance, if you need to find the identificator for `TWA 20` star
 31    in `Gaia` catalog version `DR3`, then SIMBAD will return
 32    `Gaia DR3 6132146982868270976` value for it, out of which this function
 33    will extract `6132146982868270976` as the result.
 34
 35    Example:
 36
 37    ``` py
 38    from phab.utils.databases import simbad
 39
 40    otherID = simbad.findIdentificatorFromAnotherCatalogue(
 41        "TWA 20",
 42        "gaia",
 43        "dr3"
 44    )
 45    print(otherID)
 46    ```
 47    """
 48    otherID = None
 49
 50    otherIDs = Simbad.query_objectids(starName)
 51    if otherIDs is None:
 52        logger.warning(
 53            " ".join((
 54                "SIMBAD database doesn't have information",
 55                f"about [{starName}]"
 56            ))
 57        )
 58    else:
 59        logger.debug(f"Checking SIMBAD IDs for [{starName}]:")
 60
 61        # before astroquery version 0.4.8 this table had
 62        # an upper-cased `ID` column key, but starting
 63        # with version 0.4.8 it is now lower-cased `id`
 64        #
 65        # https://github.com/astropy/astropy/issues/17695
 66        idColumnKey: str = "ID"
 67        # or compare `astroquery.__version__` with `0.4.7`
 68        if idColumnKey not in otherIDs.colnames:
 69            logger.debug(
 70                " ".join((
 71                    "There is no upper-cased [ID] key",
 72                    "in the resulting table, will try",
 73                    "with lower-cased [id] key"
 74                ))
 75            )
 76            idColumnKey = idColumnKey.lower()  # "id"
 77            if idColumnKey not in otherIDs.colnames:
 78                errorMsg = " ".join((
 79                    "SIMBAD results table has neither [ID]",
 80                    "nor [id] column"
 81                ))
 82                logger.error(errorMsg)
 83                if len(otherIDs.colnames) > 0:
 84                    logger.debug(
 85                        " ".join((
 86                            "Here are all the columns/keys",
 87                            "in this table:",
 88                            ", ".join(otherIDs.colnames)
 89                        ))
 90                    )
 91                else:
 92                    logger.debug(
 93                        " ".join((
 94                            "There are no columns/keys",
 95                            "in this table at all"
 96                        ))
 97                    )
 98                raise KeyError(errorMsg)
 99
100        for oid in otherIDs:
101            idCandidate: str = oid[idColumnKey]
102            logger.debug(f"- {idCandidate}")
103            if otherIDname.lower() in idCandidate.lower():
104                idToLookFor = (
105                    f"{otherIDname} {otherIDversion}"
106                    if otherIDversion else otherIDname
107                )
108                if idToLookFor.lower() in idCandidate.lower():
109                    if withoutIDprefix:
110                        prefixRE = re.compile(
111                            rf"{idToLookFor}\s?",
112                            re.IGNORECASE
113                        )
114                        otherID = prefixRE.sub("", idCandidate)
115                    else:
116                        otherID = idCandidate
117                    break
118    return otherID

Finds object identificator from a particular catalogue. SIMBAD returns a list of identificators prepended with the catalogue name, and this function finds the identificator for the specified catalogue and returns just the identificator. In addition to that, some catalogues have versions, so it is possible to specify which one if of interest.

For instance, if you need to find the identificator for TWA 20 star in Gaia catalog version DR3, then SIMBAD will return Gaia DR3 6132146982868270976 value for it, out of which this function will extract 6132146982868270976 as the result.

Example:

from phab.utils.databases import simbad

otherID = simbad.findIdentificatorFromAnotherCatalogue(
    "TWA 20",
    "gaia",
    "dr3"
)
print(otherID)
def getObjectID(starName: str) -> Optional[int]:
121def getObjectID(starName: str) -> Optional[int]:
122    """
123    Finds object identificator for
124    [SIMBAD tables](http://simbad.cds.unistra.fr/simbad/tap/tapsearch.html).
125    It is stored in the `oid` field of the `basic` table.
126
127    The discovery process is to compare all the known object identificators
128    with the `main_id` field value (*also from the `basic` table*). It is
129    not clear, how exactly SIMBAD maintainers choose the main ID for an object,
130    so one has to iterate through all the identificators known to SIMBAD.
131
132    Example:
133
134    ``` py
135    from phab.utils.databases import simbad
136    from typing import Optional
137
138    oid: Optional[int] = None
139    try:
140        oid = simbad.getObjectID("A2 146")
141    except KeyError as ex:
142        print(f"Something wrong with querying SIMBAD. {repr(ex)}")
143    if oid is not None:
144        print(f"Object ID: {oid}")
145    else:
146        print("No results")
147    ```
148    """
149    oid: Optional[int] = None
150
151    # check if this name is already the main ID
152    logger.debug(f"Checking whether [{starName}] is already the main ID")
153    rez = tap.queryService(
154        tap.getServiceEndpoint("simbad"),
155        " ".join((
156            "SELECT oid",
157            "FROM basic",
158            f"WHERE main_id = '{starName}'"
159        ))
160    )
161    if rez:
162        oid = rez[0]["oid"]
163        logger.debug(
164            " ".join((
165                f"- yes, that is already the main ID,",
166                "no need to iterate all the identificators.",
167                f"SIMBAD object ID is: {oid}"
168            ))
169        )
170    else:
171        logger.debug(
172            " ".join((
173                "- no, that is not the main ID, will have to iterate",
174                "all the other identificators"
175            ))
176        )
177        ids = Simbad.query_objectids(starName)
178        if ids is None:
179            logger.warning(
180                " ".join((
181                    "SIMBAD database doesn't have information",
182                    f"about [{starName}]"
183                ))
184            )
185        else:
186            logger.debug(f"Checking SIMBAD IDs for [{starName}]:")
187
188            # before astroquery version 0.4.8 this table had
189            # an upper-cased `ID` column key, but starting
190            # with version 0.4.8 it is now lower-cased `id`
191            #
192            # https://github.com/astropy/astropy/issues/17695
193            idColumnKey: str = "ID"
194            # or compare `astroquery.__version__` with `0.4.7`
195            if idColumnKey not in ids.colnames:
196                logger.debug(
197                    " ".join((
198                        "There is no upper-cased [ID] key",
199                        "in the resulting table, will try",
200                        "with lower-cased [id] key"
201                    ))
202                )
203                idColumnKey = idColumnKey.lower()  # "id"
204                if idColumnKey not in ids.colnames:
205                    errorMsg = " ".join((
206                        "SIMBAD results table has neither [ID]",
207                        "nor [id] column"
208                    ))
209                    logger.error(errorMsg)
210                    if len(ids.colnames) > 0:
211                        logger.debug(
212                            " ".join((
213                                "Here are all the columns/keys",
214                                "in this table:",
215                                ", ".join(ids.colnames)
216                            ))
217                        )
218                    else:
219                        logger.debug(
220                            " ".join((
221                                "There are no columns/keys",
222                                "in this table at all"
223                            ))
224                        )
225                    raise KeyError(errorMsg)
226
227            for id in ids:
228                idValue: str = id[idColumnKey]
229                logger.debug(f"- {idValue}")
230
231                if idValue == starName:
232                    logger.debug(
233                        f"...the [{idValue}] has already been tested, skipping"
234                    )
235                    continue
236                rez = tap.queryService(
237                    tap.getServiceEndpoint("simbad"),
238                    " ".join((
239                        "SELECT oid",
240                        "FROM basic",
241                        f"WHERE main_id = '{idValue}'"
242                    ))
243                )
244                if rez:
245                    oid = rez[0]["oid"]
246                    logger.debug(
247                        " ".join((
248                            f"The [{idValue}] is the main ID for",
249                            f"[{starName}], SIMBAD object ID is: {oid}"
250                        ))
251                    )
252                    break
253    return oid

Finds object identificator for SIMBAD tables. It is stored in the oid field of the basic table.

The discovery process is to compare all the known object identificators with the main_id field value (also from the basic table). It is not clear, how exactly SIMBAD maintainers choose the main ID for an object, so one has to iterate through all the identificators known to SIMBAD.

Example:

from phab.utils.databases import simbad
from typing import Optional

oid: Optional[int] = None
try:
    oid = simbad.getObjectID("A2 146")
except KeyError as ex:
    print(f"Something wrong with querying SIMBAD. {repr(ex)}")
if oid is not None:
    print(f"Object ID: {oid}")
else:
    print("No results")
def getStellarParameter(starName: str, table: str, param: str) -> Optional[tuple[Any, str]]:
256def getStellarParameter(
257    starName: str,
258    table: str,
259    param: str
260) -> Optional[tuple[Any, str]]:
261    """
262    A convenience function for querying SIMBAD for a stellar parameter:
263
264    1. Finds SIMBAD's object ID by the star name
265    (*with `utils.databases.simbad.getObjectID`*);
266    2. Queries for a stellar parameter by that object ID
267    (*with `utils.databases.tap.getStellarParameterFromSimbadByObjectID`*).
268
269    Example:
270
271    ``` py
272    from phab.utils.databases import simbad
273
274    starName = "PPM 725297"
275    param = "period"
276    rez = simbad.getStellarParameter(
277        starName,
278        "mesVar",
279        param
280    )
281    if rez:
282        val = rez[0]
283        ref = rez[1]
284        print(f"Value: {val}, reference: {ref}")
285    else:
286        print(f"SIMBAD doesn't have data about [{param}] parameter of [{starName}] object")
287    ```
288    """
289    rez: Optional[tuple[Any, str]] = None
290
291    oid = getObjectID(starName)
292    if oid:
293        # logger.debug(
294        #     f"Found the following object ID for [{starName}]: {oid}"
295        # )
296        rez = tap.getStellarParameterFromSimbadByObjectID(
297            oid,
298            table,
299            param
300        )
301
302    return rez

A convenience function for querying SIMBAD for a stellar parameter:

  1. Finds SIMBAD's object ID by the star name (with utils.databases.simbad.getObjectID);
  2. Queries for a stellar parameter by that object ID (with utils.databases.tap.getStellarParameterFromSimbadByObjectID).

Example:

from phab.utils.databases import simbad

starName = "PPM 725297"
param = "period"
rez = simbad.getStellarParameter(
    starName,
    "mesVar",
    param
)
if rez:
    val = rez[0]
    ref = rez[1]
    print(f"Value: {val}, reference: {ref}")
else:
    print(f"SIMBAD doesn't have data about [{param}] parameter of [{starName}] object")