Skip to content

Commit

Permalink
Merge pull request #159 from rasg-affiliates/ska
Browse files Browse the repository at this point in the history
Include access to ska array postions via ska-ost-array-config package
  • Loading branch information
DanielaBreitman authored Jan 10, 2025
2 parents 69f1e4c + ed3fcf4 commit 3353351
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 4 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/run-docs-code.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ jobs:
pip install papermill ipykernel
conda list
- name: Install Poetry
run: |
pipx install poetry
poetry --version
- name: Install ska-ost-array
run: |
git clone https://gitlab.com/ska-telescope/ost/ska-ost-array-config.git
cd ska-ost-array-config
poetry install
cd ..
- name: Install ipykernel
run: python -m ipykernel install --user --name sense --display-name "sense"

Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/testsuite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ jobs:
run: |
pip install .[test]
conda list
- name: Install Poetry
run: |
pipx install poetry
poetry --version
- name: Install ska-ost-array
run: |
git clone https://gitlab.com/ska-telescope/ost/ska-ost-array-config.git
cd ska-ost-array-config
poetry install
cd ..
- name: Run Tests
run: |
Expand Down
41 changes: 41 additions & 0 deletions docs/tutorials/using_builtin_observatories.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,47 @@
"print(obs.frequency)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can also create an observatory from a specific SKA configuration provided by `ska-ost-array-config` package (see https://gitlab.com/ska-telescope/ost/ska-ost-array-config for installation instructions) using the `from_ska` method:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"obs = Observatory.from_ska(\n",
" subarray_type=\"AA*\", array_type=\"low\", Trcv=100.0 * un.K, frequency=75.0 * un.MHz\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can create a custom array by passing additional parameters to the array class (see `ska-ost-array-config` documentation). For example, to select all core stations, add 6 stations in the E1 cluster, and remove two stations from the core array:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"custom_obs = Observatory.from_ska(\n",
" subarray_type=\"custom\",\n",
" array_type=\"low\",\n",
" Trcv=100.0 * un.K,\n",
" frequency=75.0 * un.MHz,\n",
" custom_stations=\"C*, E1-*\",\n",
" exclude_stations=\"C1,C2\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
57 changes: 54 additions & 3 deletions src/py21cmsense/observatory.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Observatory:
Note that longitude is not required, as we assume an isotropic sky.
Trcv
Receiver temperature, either a temperature Quantity, or a callable that
taakes a single frequency Quantity and returns a temperature Quantity.
takes a single frequency Quantity and returns a temperature Quantity.
min_antpos, max_antpos
The minimum/maximum radial distance to include antennas (from the origin
of the array). Assumed to be in units of meters if no units are supplied.
Expand Down Expand Up @@ -204,7 +204,8 @@ def from_profile(cls, profile: str, frequency: tp.Frequency | None = None, **kwa
----------
profile
A string label identifying the observatory. Available built-in observatories
can be obtained with :func:`get_builtin_profiles`.
can be obtained with :func:`get_builtin_profiles`. For more up-to-date SKA profiles,
check the :func:`from_ska` method.
frequency
The frequency at which to specify the observatory.
Expand All @@ -222,6 +223,56 @@ def from_profile(cls, profile: str, frequency: tp.Frequency | None = None, **kwa
obj = cls.from_yaml(fl, frequency=frequency)
return obj.clone(**kwargs)

@classmethod
def from_ska(
cls,
subarray_type: str,
array_type: str = "low",
Trcv: tp.Temperature | Callable = 100 * un.K, # noqa N803
frequency: tp.Frequency | None = 150.0 * un.MHz,
**kwargs,
) -> Observatory:
"""Instantiate an SKA Observatory.
Parameters
----------
subarray_type
The type of subarray to use. Options are "AA4", "AA*", "AA1", "AA2", "AA0.5",
and "custom"
array_type, optional
The type of array to use. Options are "low" and "mid".
Default is "low".
Trcv, optional
Receiver temperature, either a temperature Quantity, or a callable that
takes a single frequency Quantity and returns a temperature Quantity.
Default is 100 K.
frequency, optional
The frequency at which to specify the observatory. Default is 150 MHz.
Other Parameters
----------------
All other parameters passed will be passed into the LowSubArray or MidSubArray class.
See the documentation of the ska-ost-array-config package for more information.
"""
try:
from ska_ost_array_config.array_config import LowSubArray, MidSubArray
except ImportError as exception: # pragma: no cover
raise ImportError(
"ska-ost-array-config package is required, "
+ "see https://gitlab.com/ska-telescope/ost/ska-ost-array-config"
) from exception

if array_type == "low":
subarray = LowSubArray(subarray_type, **kwargs)
elif array_type == "mid":
subarray = MidSubArray(subarray_type, **kwargs)
else:
raise ValueError("array_type must be 'low' or 'mid'.")
antpos = subarray.array_config.xyz.data * un.m
_beam = beam.GaussianBeam(frequency=frequency, dish_size=35.0 * un.m)
lat = subarray.array_config.location.lat.rad * un.rad
return cls(antpos=antpos, beam=_beam, latitude=lat, Trcv=Trcv)

@cached_property
def baselines_metres(self) -> tp.Meters:
"""Raw baseline distances in metres for every pair of antennas.
Expand Down Expand Up @@ -473,7 +524,7 @@ def grid_baselines(
--------
grid_baselines_coherent :
Coherent sum over baseline groups of the output of this method.
grid_basleine_incoherent :
grid_baseline_incoherent :
Incoherent sum over baseline groups of the output of this method.
"""
if baselines is not None:
Expand Down
57 changes: 56 additions & 1 deletion tests/test_observatory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import pytest
import pyuvdata
from astropy import units
from astropy.coordinates import EarthLocation
from astropy.coordinates import EarthLocation, SkyCoord
from astropy.time import Time

from py21cmsense import Observatory
from py21cmsense.baseline_filters import BaselineRange
Expand Down Expand Up @@ -205,6 +206,60 @@ def test_from_yaml(bm):
Observatory.from_yaml(3)


def test_from_ska():
pytest.importorskip("ska_ost_array_config")

from ska_ost_array_config import UVW
from ska_ost_array_config.array_config import LowSubArray
from ska_ost_array_config.simulation_utils import simulate_observation

obs = Observatory.from_ska(subarray_type="AA*", array_type="low", frequency=300.0 * units.MHz)
low_aastar = LowSubArray(subarray_type="AA*")
assert obs.antpos.shape == low_aastar.array_config.xyz.data.shape
Observatory.from_ska(subarray_type="AA*", array_type="mid", frequency=300.0 * units.MHz)
obs = Observatory.from_ska(subarray_type="AA4", array_type="low", frequency=300.0 * units.MHz)
low_aa4 = LowSubArray(subarray_type="AA4")
assert obs.antpos.shape == low_aa4.array_config.xyz.data.shape
obs = Observatory.from_ska(
subarray_type="custom",
array_type="low",
Trcv=100.0 * units.K,
frequency=150.0 * units.MHz,
custom_stations="C*,E1-*",
exclude_stations="C1,C2",
)
low_custom = LowSubArray(
subarray_type="custom", custom_stations="C*,E1-*", exclude_stations="C1,C2"
) # selects all core stations, 6 stations in the E1 cluster, excludes core stations C1 and C2
assert obs.antpos.shape == low_custom.array_config.xyz.data.shape

# Simulate visibilities and retreive the UVW values
ref_time = Time.now()
zenith = SkyCoord(
alt=90 * units.deg,
az=0 * units.deg,
frame="altaz",
obstime=ref_time,
location=low_custom.array_config.location,
).icrs
vis = simulate_observation(
array_config=low_custom.array_config,
phase_centre=zenith,
start_time=ref_time,
ref_freq=50e6, # Dummy value. We are after uvw values in [m]
chan_width=1e3, # Dummy value. We are after uvw values in [m]
n_chan=1,
)
uvw = UVW.UVW(vis, ignore_autocorr=False)
uvw_m = uvw.uvdist_m
assert np.allclose(obs.longest_baseline / obs.metres_to_wavelengths, uvw_m.max() * units.m)

with pytest.raises(ValueError, match="array_type must be"):
Observatory.from_ska(
subarray_type="AA*", array_type="non-existent", frequency=300.0 * units.MHz
)


def test_get_redundant_baselines(bm):
a = Observatory(antpos=np.array([[0, 0, 0], [1, 0, 0], [2, 0, 0]]) * units.m, beam=bm)

Expand Down

0 comments on commit 3353351

Please sign in to comment.