Source code for py21cmsense.antpos
"""A module defining functions to produce antenna positions algorithmically.
Each function here defined may take arbitrary parameters, but must return
a single array of shape (Nant, 3) with units of meters, corresponding to (x,y,z) positions
of antennae centred at zero.
"""
from __future__ import annotations
import numpy as np
from astropy import units as un
from . import units as tp
from . import yaml
[docs]
@yaml.yaml_func()
def hera(
hex_num: int,
separation: tp.Length = 14 * un.m,
split_core: bool = False,
outriggers: bool = False,
row_separation: tp.Length | None = None,
) -> tp.Meters:
"""
Produce a simple regular hexagonal array.
Note that the returned order of the core array antennas matches the antenna
numbering used by the HERA team.
Parameters
----------
hex_num
Number of antennas per side of the hexagon
separation
The distance between antennas along a side.
May have units of distance or time, the latter interpreted as a distance travelled
by light.
split_core
Whether to split the hex into three offset segments.
outriggers
Whether to add outrigger antennas.
Returns
-------
antpos
A 2D array of antenna positions, shape ``(Nants, 3)``.
"""
if hex_num <= 1:
raise ValueError("hex_num must be greater than 1")
sep = separation.to_value("m")
row_sep = sep * np.sqrt(3) / 2 if row_separation is None else row_separation.to_value("m")
# construct the main hexagon
positions = []
for row in range(-hex_num + split_core + 1, hex_num):
# adding split_core deletes a row if it's true
for col in range(2 * hex_num - abs(row) - 1):
x_pos = sep * ((2 - (2 * hex_num - abs(row))) / 2 + col)
y_pos = row * row_sep
positions.append([x_pos, y_pos, 0])
# basis vectors (normalized to sep)
up_right = sep * np.asarray([0.5, row_sep / sep, 0])
up_left = sep * np.asarray([-0.5, row_sep / sep, 0])
# split the core if desired
if split_core:
new_pos = []
for pos in positions:
# find out which sector the antenna is in
theta = np.arctan2(pos[1], pos[0])
if pos[0] == 0 and pos[1] == 0:
new_pos.append(pos)
elif -np.pi / 3 < theta < np.pi / 3:
new_pos.append(np.asarray(pos) + (up_right + up_left) / 3)
elif np.pi / 3 <= theta < np.pi:
new_pos.append(np.asarray(pos) + up_left - (up_right + up_left) / 3)
else:
new_pos.append(pos)
# update the positions
positions = new_pos
# add outriggers if desired
if outriggers:
# The specific displacements of the outrigger sectors are
# designed specifically for redundant calibratability and
# "complete" uv-coverage, but also to avoid specific
# obstacles on the HERA site (e.g. a road to a MeerKAT antenna)
exterior_hex_num = outriggers + 2
for row in range(exterior_hex_num - 1, -exterior_hex_num, -1):
for col in range(2 * exterior_hex_num - abs(row) - 1):
x_pos = ((2 - (2 * exterior_hex_num - abs(row))) / 2 + col) * sep * (hex_num - 1)
y_pos = row * (hex_num - 1) * row_sep
theta = np.arctan2(y_pos, x_pos)
if np.sqrt(x_pos**2 + y_pos**2) > sep * (hex_num + 1):
if 0 < theta <= 2 * np.pi / 3 + 0.01:
positions.append(
np.asarray([x_pos, y_pos, 0]) - 4 * (up_right + up_left) / 3
)
elif 0 >= theta > -2 * np.pi / 3:
positions.append(
np.asarray([x_pos, y_pos, 0]) - 2 * (up_right + up_left) / 3
)
else:
positions.append(
np.asarray([x_pos, y_pos, 0]) - 3 * (up_right + up_left) / 3
)
return np.array(positions) * un.m