Point source sampler (ptsrc_sampler
)
calc_proj_operator(ra, dec, fluxes, ant_pos, antpairs, freqs, times, beams, latitude=-0.5361913261514378)
Calculate a visibility vector for each point source, as a function of frequency, time, and baseline. This is the projection operator from point source amplitude to visibilities. Gains are not included.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
ra,
|
dec (array_like
|
RA and Dec of each source, in radians. |
required |
fluxes
|
array_like
|
Flux for each point source as a function of frequency. |
required |
ant_pos
|
dict
|
Dictionary of antenna positions, [x, y, z], in m. The keys should be the numerical antenna IDs. |
required |
antpairs
|
list of tuple
|
List of tuples containing pairs of antenna IDs, one for each baseline. |
required |
freqs
|
array_like
|
Frequencies, in MHz. |
required |
times
|
array_like
|
LSTs, in radians. |
required |
beams
|
list of UVBeam
|
List of UVBeam objects, one for each antenna. |
required |
latitude
|
float
|
Latitude of the observing site, in radians. |
-0.5361913261514378
|
Returns:
Name | Type | Description |
---|---|---|
vis_proj_operator |
array_like
|
The projection operator from source amplitudes to visibilities,
from |
Source code in hydra/ptsrc_sampler.py
def calc_proj_operator(
ra,
dec,
fluxes,
ant_pos,
antpairs,
freqs,
times,
beams,
latitude=-0.5361913261514378,
):
"""
Calculate a visibility vector for each point source, as a function of
frequency, time, and baseline. This is the projection operator from point
source amplitude to visibilities. Gains are not included.
Parameters:
ra, dec (array_like):
RA and Dec of each source, in radians.
fluxes (array_like):
Flux for each point source as a function of frequency.
ant_pos (dict):
Dictionary of antenna positions, [x, y, z], in m. The keys should
be the numerical antenna IDs.
antpairs (list of tuple):
List of tuples containing pairs of antenna IDs, one for each
baseline.
freqs (array_like):
Frequencies, in MHz.
times (array_like):
LSTs, in radians.
beams (list of UVBeam):
List of UVBeam objects, one for each antenna.
latitude (float):
Latitude of the observing site, in radians.
Returns:
vis_proj_operator (array_like):
The projection operator from source amplitudes to visibilities,
from `calc_proj_operator`. This is an array of the visibility
values for each source.
"""
Nptsrc = ra.size
Nants = len(ant_pos)
Nvis = len(antpairs)
# Empty array of per-point source visibilities
vis_ptsrc = np.zeros((Nvis, freqs.size, times.size, Nptsrc), dtype=np.complex128)
# Get visibility for each point source
# Returns shape (NFREQS, NTIMES, NANTS, NANTS, NSRCS)
vis = simulate_vis_per_source(
ants=ant_pos,
fluxes=fluxes,
ra=ra,
dec=dec,
freqs=freqs * 1e6,
lsts=times,
beams=beams,
polarized=False,
precision=2,
latitude=latitude,
use_feed="x",
)
# Allocate computed visibilities to only available baselines (saves memory)
ants = list(ant_pos.keys())
for i, bl in enumerate(antpairs):
idx1 = ants.index(bl[0])
idx2 = ants.index(bl[1])
vis_ptsrc[i, :, :, :] = vis[:, :, idx1, idx2, :]
return vis_ptsrc
precompute_mpi(comm, ants, antpairs, freq_chunk, time_chunk, proj_chunk, data_chunk, inv_noise_var_chunk, gain_chunk, amp_prior_std, realisation=True)
Precompute the projection operator and matrix operator in parallel.
The projection operator is computed in chunks in time and frequency. The overall matrix operator can be computed by summing the matrix operator for the time and frequency chunks.
Source code in hydra/ptsrc_sampler.py
def precompute_mpi(
comm,
ants,
antpairs,
freq_chunk,
time_chunk,
proj_chunk,
data_chunk,
inv_noise_var_chunk,
gain_chunk,
amp_prior_std,
realisation=True,
):
"""
Precompute the projection operator and matrix operator in parallel.
The projection operator is computed in chunks in time and frequency.
The overall matrix operator can be computed by summing the matrix
operator for the time and frequency chunks.
"""
# Make sure ants is an array
ants = np.atleast_1d(ants)
if comm is not None:
myid = comm.Get_rank()
else:
myid = 0
# Check input dimensions
assert data_chunk.shape == (len(antpairs), freq_chunk.size, time_chunk.size)
assert data_chunk.shape == inv_noise_var_chunk.shape
proj = proj_chunk.copy() # make a copy so we don't alter the original proj!
# FIXME: Check for unused args!
# Apply gains to projection operator
for k, bl in enumerate(antpairs):
ant1, ant2 = bl
i1 = np.where(ants == ant1)[0][0]
i2 = np.where(ants == ant2)[0][0]
proj[k, :, :, :] *= (
gain_chunk[i1, :, :, np.newaxis] * gain_chunk[i2, :, :, np.newaxis].conj()
)
# (2) Precompute linear system operator
nsrcs = proj.shape[-1]
my_linear_op = np.zeros((nsrcs, nsrcs), dtype=proj.real.dtype)
# inv_noise_var has shape (Nbls, Nfreqs, Ntimes)
v_re = (proj.real * np.sqrt(inv_noise_var_chunk[..., np.newaxis])).reshape(
(-1, nsrcs)
)
v_im = (proj.imag * np.sqrt(inv_noise_var_chunk[..., np.newaxis])).reshape(
((-1, nsrcs))
)
# Treat real and imaginary separately, and get copies, to massively
# speed-up the matrix multiplication!
my_linear_op[:, :] = v_re.T @ v_re + v_im.T @ v_im
del v_re, v_im
# Do Reduce (sum) operation to get total operator on root node
linear_op = np.zeros(
(1, 1), dtype=my_linear_op.dtype
) # dummy data for non-root workers
if myid == 0:
linear_op = np.zeros_like(my_linear_op)
if comm is not None:
comm.Reduce(my_linear_op, linear_op, op=MPI_SUM, root=0)
else:
linear_op = my_linear_op
# Include prior and identity terms to finish constructing LHS operator on root worker
if myid == 0:
linear_op = np.eye(linear_op.shape[0]) + np.diag(
amp_prior_std
) @ linear_op @ np.diag(amp_prior_std)
# (3) Calculate linear system RHS
proj = proj.reshape((-1, nsrcs))
realisation_switch = (
1.0 if realisation else 0.0
) # Turn random realisations on or off
# Calculate residual of data vs fiducial model (residual from amplitudes = 1)
# (proj now includes gains)
resid_chunk = data_chunk.copy() - (
proj.reshape((-1, nsrcs)) @ np.ones_like(amp_prior_std)
).reshape(data_chunk.shape)
# (Terms 1+3): S^1/2 A^\dagger [ N^{-1} r + N^{-1/2} \omega_r ]
omega_n = (
realisation_switch
* (
1.0 * np.random.randn(*resid_chunk.shape)
+ 1.0j * np.random.randn(*resid_chunk.shape)
)
/ np.sqrt(2.0)
)
# Separate complex part of RHS into real and imaginary parts, and apply
# the real and imaginary parts of the projection operator separately.
# This is necessary to get a real RHS vector
y = (
(resid_chunk * inv_noise_var_chunk) + (omega_n * np.sqrt(inv_noise_var_chunk))
).flatten()
b = amp_prior_std * (proj.T.real @ y.real + proj.T.imag @ y.imag)
# Reduce (sum) operation on b
linear_rhs = np.zeros((1,), dtype=b.dtype) # dummy data for non-root workers
if myid == 0:
linear_rhs = np.zeros_like(b)
if comm is not None:
comm.Reduce(b, linear_rhs, op=MPI_SUM, root=0)
else:
linear_rhs = b
# (Term 2): \omega_a
if myid == 0:
linear_rhs += realisation_switch * np.random.randn(nsrcs) # real vector
return linear_op, linear_rhs