"""
Allocate apertures to targets.
Contains code originally written by 2021 Akamai intern, Brittany Ann
Ramos.
.. include:: ../include/links.rst
"""
from pathlib import Path
from IPython import embed
import numpy
[docs]def random_targets(r, n=None, density=5., rng=None):
r"""
Draw a set of random x and y coordinates within a circle.
Args:
r (:obj:`float`):
Radius of the circle.
n (:obj:`int`, optional):
The total number of points to draw. If None, number drawn
based on the density requested.
density (:obj:`float`, optional):
The average density of targets within the circle. This is
used to calculate the number of points to generate within
the circle: ``n = int(numpy.ceil(density*numpy.pi*r**2))``. Units
must be appropriate match radius units.
rng (`numpy.random.Generator`_, optional):
Random number generator to use. If None, a new one is
instantiated using `numpy.random.default_rng`_.
Returns:
:obj:`tuple`: Two vectors of length :math:`N_{\rm targ}` (the number of
targets). Cartesian x coordinates are in the first vector, y
coordinates in the second.
"""
# Calculate the number of points to match an expected density
if n is None:
n = int(numpy.ceil(density*numpy.pi*r**2))
if rng is None:
rng = numpy.random.default_rng()
c = numpy.empty((0,2), dtype=float)
overdraw = 1.5
while c.shape[0] != n:
# Draw more points than needed within the unit square until the correct
# number is reached.
# TODO: Probably a less brute-force way of doing this...
c = rng.uniform(low=-1, high=1, size=(int(n*overdraw),2))
# Find those within the r = 1
indx = c[:,0]**2 + c[:,1]**2 < 1
c = c[indx][:n]
# Increase overdraw for next iteration
overdraw *= 1.1
return r*c[:,0], r*c[:,1]
[docs]def parse_targets(ifile, ra_c=1, dec_c=2, ap_c=None, default_ap=0):
"""
Parse target coordinates and aperture types from an input file.
Args:
ifile (:obj:`str`):
Columnated ascii file with the target coordinates.
ra_c (:obj:`int`, optional):
1-indexed column with the RA coordinates. Assumed to be in decimal
degrees.
dec_c (:obj:`int`, optional):
1-indexed column with the declination coordinates. Assumed to be in
decimal degrees.
ap_c (:obj:`int`, optional):
1-indexed column with the aperture type to assign to each target.
If None, the type is not available in the input file and the
``default_types`` is used for all targets. Apertures must be 0 for
a single fiber or 1 for an IFU.
default_ap (:obj:`int`, optional):
If the aperture types are not provided in the file, this sets the
type to assign to *all* apertures. Apertures must be 0 for a single
fiber or 1 for an IFU.
Returns:
:obj:`tuple`: Three numpy vectors with the coordinates and aperture type
for each target.
"""
# Check the input
if default_ap not in [0, 1]:
raise ValueError('Default aperture type must be 0 (single-fiber) or 1 (IFU).')
# Instantiate the file path
p = Path(ifile).resolve()
# Check it exists
if not p.exists():
raise FileNotFoundError(f'{str(p)}')
# Read the file
db = numpy.genfromtxt(str(p), dtype=str).T
# Check the requested columns exist
if numpy.any(numpy.array([ra_c, dec_c, 1 if ap_c is None else ap_c]) > db.shape[0]):
raise ValueError(f'{p.name} only contains {db.shape[0]} columns. Check column requests.')
# Collect the data and convert to the correct type
ra = db[ra_c-1].astype(float)
dec = db[dec_c-1].astype(float)
ap = numpy.full(ra.size, default_ap, dtype=int) if ap_c is None else db[ap_c-1].astype(int)
return ra, dec, ap