Skip to content

Commit

Permalink
feat(ch_ephem): CHIME ephemeris refactored from ch_util.
Browse files Browse the repository at this point in the history
Extracts a lot of `ch_util.ephemeris` into a separate package.

General changes:

* Solar and lunar ephemeris functions have been converted into
  `caput.time.Observer` methods: radiocosmology/caput#271
* Other functions that take a `Observer` object, now use `None`
  as a default, and load the `chime` observer during execution if
  necessary.

More comments on specific submodules follow.

Catalogs:

This contains all the `ch_util/catalogs` plus a function
(`ch_ephem.catalogs.load`) to read them and return the parsed JSON.

The standard source catalogues loaded by the `sources` submodule need
to move to `ch_ephem`.  The other catalogues could stay in `ch_util`,
but it seems simpler to me to keep them all in the same place and
provide a uniform interface to them.

Coord:

Co-ordinate transforms etc.  These are essentially unchanged from
their versions in `ch_util.ephemeris`.

Observers:

Re-creates all the `caput.time.Observers` which used to be created
by `ch_util.ephemeris`, but for the most part, they have to be
fetched via name from the `get()` function.  The exception to this
is the `chime` Observer, which can be directly imported, since it's
the one usually needed.

Position data for the observers is now in a YAML data file
`instruments.yaml` which is parsed at import time.  New instrument
`Observer` objects can be added to `ch_ephem` just by adding information
for it to the YAML file.

Also note that `Observers` returned by this submodule have an extra
attribute, `rotation`, inserted into their `__dict__`, which is not
in `caput`.  This rotation is needed by `ch_ephem.coord.peak_ra`.
If we don't want to do this to these `Observers`, one other options
would be to keep `peak_ra` in `ch_util` (moving it to, say,
`ch_util.tools`), and then there's no need for rotation information
in this module.

Like `sources`, this submodule reads data from disk at import time,
so is not automatically imported into the base `ch_ephem` object.

Pointing:

Just Galt telescope pointing routines, unchanged from
`ch_util.ephemeris`.  These are another candidate we could consider
putting somewhere else in ch_util, if we don't want them here.

Sources:

Loads the standard source catalogs, and provides the calibrator objects
`CasA`, `CygA`, `TauA`, `VirA`.  Like `observers`, this submodule
reads data from disk at import time, so is not automatically imported
into the base `ch_ephem` object.

Time:

Time utilities, largely unchanged from what was in `ch_util.ephemeris`.
  • Loading branch information
ketiltrout committed Jul 26, 2024
1 parent 22b9b1f commit 5247b97
Show file tree
Hide file tree
Showing 15 changed files with 39,659 additions and 4 deletions.
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 .
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# ch_ephem
# CHIME ephemeris

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.

CHIME ephemeris
35 changes: 34 additions & 1 deletion ch_ephem/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
pass
"""CHIME ephemeris routines
Note: the submodules `observers` and `sources`
read data from disk at import time.
Submodules
==========
.. 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

try:
__version__ = version("ch_ephem")
except PackageNotFoundError:
# package is not installed
pass

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

from __future__ import annotations
from typing import TYPE_CHECKING

import json
import pathlib

if TYPE_CHECKING:
from collections.abc import Iterable
del TYPE_CHECKING


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

0 comments on commit 5247b97

Please sign in to comment.