Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ch_ephem): CHIME ephemeris refactored from ch_util. #1

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/linting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: ch_ephem-linting
on:
pull_request:
branches:
- master
push:
branches:
- master

jobs:

black-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: "3.11"

- name: Install black
run: pip install black

- name: Check code with black
run: black --check .

ruff-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install ruff
run: pip install ruff

- name: Install ch_ephem
run: pip install .

- name: Run ruff
run: ruff check .
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# ch_ephem
# CHIME ephemeris

CHIME ephemeris
This package provides the CHIME ephemeris routines, `ch_ephem`. Most
of this code originated in `ch_util`.

For general-purpose ephemeris routines, see `caput`.
105 changes: 104 additions & 1 deletion ch_ephem/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,104 @@
pass
"""CHIME ephemeris routines
Instrument Observrers and General Ephemeris Routines
====================================================
Any ephemeris routine which needs to know the location of the
observer on the Earth are accessible via instrument Observer
instances obtainable through :py:meth:`ch_ephem.observers.get`:
>>> import ch_ephem.observers
>>> pathfinder = ch_ephem.observers.get("pathfinder")
>>> pathfinder.solar_transit(...)
The `Observer` instances returned by this method are subclassed from
`caput.time.Observers` and can be used as normal `caput` observers.
Location and geometry data for the instrument observers are defined
in the data file `instruments.yaml` provided as part of `ch_ephem`.
Celestial Intermediate Reference System
=======================================
The precession of the Earth's axis gives noticeable shifts in object
positions over the life time of CHIME. To minimise the effects of this we
need to be careful and consistent with our ephemeris calculations.
Historically Right Ascension has been given with respect to the Vernal
Equinox which has a significant (and unnecessary) precession in the origin of
the RA axis. To avoid this we use the new Celestial Intermediate Reference
System which does not suffer from this issue.
Practically this means that when calculating RA, DEC coordinates for a source
position at a *given time* you must be careful to obtain CIRS coordinates
(and not equinox based ones). Internally using `ch_ephem.coord.object_coords` does
exactly that for you, so for any lookup of coordinates you should use that on
your requested body.
Note that the actual coordinate positions of sources must be specified using
RA, DEC coordinates in ICRS (which is roughly equivalent to J2000). The
purpose of `object_coords` is to transform into new RA, DEC coordinates taking
into account the precession and nutation of the Earth's polar axis since
then.
These kind of coordinate issues are tricky, confusing and hard to debug years
later, so if you're unsure you are recommended to seek some advice.
Radio Source Catalogs
=====================
This package provides several radio source catalogues used by CHIME. The
standard radio source catalogue used by CHIME is available as
`ch_ephem.sources.source_dictionary`:
>>> from ch_ephem.sources import source_dictionary
>>> source_dictionary['CAS_A'].ra
<Angle 23h 23m 27.94s>
The four standard CHIME cailbrators are also available by name:
>>> from ch_ephem.sources import CasA, CygA, VirA, TauA
>>> CasA.ra
<Angle 23h 23m 27.94s>
Additional catalogues which are not part of the standard radio source catalogue
are also available through the `ch_ephem.catalogs` module. Any catalog can be
accessed using :py:meth:`ch_ephem.catalogs.load`, which takes a catalogue name
and returns a dict containing the parsed JSON representation of the catalog:
>>> import ch_ephem.catalogs
>>> ch_ephem.catalogs.list()
['atnf_psrcat', 'hfb_target_list', 'primary_calibrators_perley2016', 'specfind_v2_5Jy_vollmer2009']
>>> perley2016 = ch_ephem.catalogs.load('primary_calibrators_perley2016')
>>> perley2016["CAS_A"]["ra"]
350.86642
Submodules
==========
Note: the submodules `observers` and `sources` read data from disk at import time.
.. autosummary::
:toctree: _autosummary
catalogs
coord
observers
pointing
sources
time
"""

__all__ = ["catalogs", "coord", "pointing", "time", "__version__"]

# We deliberately do not import .observers and .sources here
# because they both perform reads from disk
from . import catalogs, coord, pointing, time

from importlib.metadata import version, PackageNotFoundError

ketiltrout marked this conversation as resolved.
Show resolved Hide resolved
try:
__version__ = version("ch_ephem")
except PackageNotFoundError:
# package is not installed
pass

del version, PackageNotFoundError
64 changes: 64 additions & 0 deletions ch_ephem/catalogs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""CHIME ephemeris source catalogues
Functions
=========
- :py:meth:`list`
- :py:meth:`load`
"""

from __future__ import annotations
from typing import TYPE_CHECKING

import glob
import json
import pathlib

if TYPE_CHECKING:
from collections.abc import Iterable
del TYPE_CHECKING

ketiltrout marked this conversation as resolved.
Show resolved Hide resolved

def list() -> list[str]:
"""List available catalogues.
Returns
-------
catalogs : list
A list with the names of available catalogs
"""

cats = sorted(glob.glob("*.json", root_dir=pathlib.Path(__file__).parent))

# Strip ".json" off the end
return [cat[:-5] for cat in cats]


def load(name: str) -> Iterable:
"""Read the named catalogue and return the parsed JSON representation.
Parameters
----------
name : str
The name of the catalogue to load. This is the name of a JSON file
in the `ch_ephem/catalogs` directory, excluding the `.json` suffix.
Returns
-------
catalogue: Iterable
The parsed catalogue.
Raises
------
JSONDecodeError:
An error occurred while trying to parse the catalogue.
ValueError:
No catalogue with the given name could be found.
"""
path = pathlib.Path(__file__).with_name(name + ".json")

try:
with path.open() as f:
return json.load(f)
except FileNotFoundError as e:
raise ValueError(f"No such catalogue: {name}") from e
Loading
Loading