Finishing implementation and doc strings

ahmedhosssam

Ahmed Hossam

Posted on June 17, 2024

Finishing implementation and doc strings

The main implementation and documentation strings phase has been finished.

One of the functions that have been deleted was parse_unit, it almost wasn't triggered by any event of HEK events, so I decided to delete it, and we will see in the future if it has any major effects on the code.

Also parse_columns_to_table has been refactored by adding more variables to make the code more readable.

This is the new implementation:

def parse_columns_to_table(table, attributes, is_coord_prop = False):
    """
    Parses the columns in an Astropy table and convert the values into Astropy objects.

    Parameters
    ----------
    table: astropy.table
        Astropy table.
    attributes: list
        A list of HEK unit attributes or coordinate attributes.
    is_coord_prop: bool
        To specify if `attributes` is a list of unit attributes or coordinate attributes.

    Returns
    -------
    `astropy.table`

    Raises
    ------
    TypeError
        If `table` is not an Astropy table.
    KeyError
        If any of the attribute dictionaries are missing required keys (i.e. "name", "unit_prop").
    """
    for attribute in attributes:
        if attribute["name"] in table.colnames and ("unit_prop" in attribute or attribute.get("is_chaincode", False)) and attribute.get("is_unit_prop", True):
            unit_attr = ""
            if is_coord_prop:
                unit_attr = "event_coordunit"
            else:
                unit_attr = attribute["unit_prop"]

            new_column = []
            for idx, value in enumerate(table[attribute["name"]]):
                new_value = ""
                if value in ["", None]:
                    new_value = value
                elif attribute.get("is_chaincode", False):
                    new_value = parse_chaincode(value, attribute, table[attribute["unit_prop"]][idx])
                else:
                    unit = get_unit(table[unit_attr][idx])
                    new_value = value * unit
                new_column.append(new_value)

            table[attribute["name"]] = new_column

    for attribute in attributes:
        if attribute.get("is_unit_prop", False) and attribute["name"] in table.colnames:
            del table[attribute["name"]]
    return table

Enter fullscreen mode Exit fullscreen mode

Also a public interface __all__ has been added to util.py, until now, the public "api" for util.py is:

__all__ = [
    'freeze',
    'parse_times',
    'parse_values_to_quantities',
    'UNIT_FILE_PATH',
    'COORD_FILE_PATH'
]
Enter fullscreen mode Exit fullscreen mode

UNIT_FILE_PATH and COORD_FILE_PATH are used in test_hek.py

There was a problem in get_unit because of some units that wasn't parsed correctly, one of them is ergs per cubic centimeter, because of the first implementation of get_unit:

with u.add_enabled_units([cm2, m2, m3]), u.set_enabled_aliases(aliases):
        # Units for coordinate frames have more than one unit, otherwise it will be just one unit.
        # There is an assumption that coord1_unit, coord2_unit and coord3_unit are the same.
        units = re.split(r'[, ]', unit)
        return u.Unit(units[0].lower())
Enter fullscreen mode Exit fullscreen mode

The function was taking the first "word" of the input and returns the unit, obviously this will be wrong with ergs per cubic centimeter because it will take only ergs and returns u.Unit('erg').
But this is the only case with HEK units, the other units works just fine.
This is the new implementation to correct this behavior:

def get_unit(unit):
    """
    Converts string into astropy unit.

    Parameters
    ----------
    unit: str
        The targeted unit

    Returns
    -------
    unit
        Astropy unit object (e.g. <class 'astropy.units.core.Unit'> or <class 'astropy.units.core.CompositeUnit'>)

    Raises
    ------
    ValueError
        Because `unit` did not parse as unit.

    Notes
    ----
    For the complete list of HEK parameters: https://www.lmsal.com/hek/VOEvent_Spec.html

    """
    cm2 = u.def_unit("cm2", u.cm**3)
    m2 = u.def_unit("m2", u.m**2)
    m3 = u.def_unit("m3", u.m**3)
    erg_per_cm3 = u.def_unit("ergs/cm^3", u.erg/u.ml)

    aliases = {
        "steradian": u.sr,
        "arcseconds": u.arcsec,
        "degrees": u.deg,
        "sec": u.s,
        "emx": u.Mx,
        "amperes": u.A,
        "ergs": u.erg,
        "cubic centimeter": u.ml,
        "square centimeter": cm2,
        "cubic meter": m3,
        "square meter": m2,
        "ergs per cubic centimeter": erg_per_cm3,
    }
    with u.add_enabled_units([cm2, m2, m3]), u.set_enabled_aliases(aliases), warnings.catch_warnings():
        # Units for coordinate frames have more than one unit, otherwise it will be just one unit.
        # There is an assumption that coord1_unit, coord2_unit and coord3_unit are the same.
        warnings.filterwarnings("ignore", category=u.UnitsWarning)
        if unit in aliases:
            unit = u.Unit(aliases[unit])
        else:
            unit = u.Unit(re.split(r'[, ]', unit)[0].lower())
        return unit
Enter fullscreen mode Exit fullscreen mode

Also, the units warnings were ignored because it's irrelevant to the user of HEK.

So, the next phase is testing, we will see if our assumptions about the unit parsing and the other refactoring were right or not.

💖 💪 🙅 🚩
ahmedhosssam
Ahmed Hossam

Posted on June 17, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related