Spherical harmonic sampler (sh_sampler
)
alms2healpy(alms, lmax)
Takes a real array split as [real, imag] (without the m=0 modes imag-part) and turns it into a complex array of alms (positive modes only) ordered as in HEALpy.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
alms
|
array_like
|
Array of zeros except for the specified mode. The array represents all positive (+m) modes including zero and has double length, as real and imaginary values are split. The first half is the real values. |
required |
Returns:
Name | Type | Description |
---|---|---|
healpy_modes |
array_like
|
Array of zeros except for the specified mode. The array represents all positive (+m) modes including zeroth modes. |
Source code in hydra/sh_sampler.py
def alms2healpy(alms, lmax):
"""
Takes a real array split as [real, imag] (without the m=0 modes
imag-part) and turns it into a complex array of alms (positive
modes only) ordered as in HEALpy.
Parameters:
alms (array_like):
Array of zeros except for the specified mode.
The array represents all positive (+m) modes including zero
and has double length, as real and imaginary values are split.
The first half is the real values.
Returns:
healpy_modes (array_like):
Array of zeros except for the specified mode.
The array represents all positive (+m) modes including zeroth modes.
"""
if len(alms.shape) == 1:
# Combine real and imaginary parts into alm format expected by healpy
real_imag_split_index = int((np.size(alms) + (lmax + 1)) / 2)
real = alms[:real_imag_split_index]
add_imag_m0_modes = np.zeros(lmax + 1) # add m=0 imag. modes back in
imag = np.concatenate((add_imag_m0_modes, alms[real_imag_split_index:]))
healpy_modes = real + 1.0j * imag
return healpy_modes
elif len(alms.shape) == 2:
# Handle 2D array case (loop over entries) with a recursion
return np.array([alms2healpy(modes, lmax) for modes in alms])
else:
raise ValueError("alms array must either have shape (Nmodes,) or (Nmaps, Nmodes)")
apply_lhs_no_rot(a_cr, inv_noise_var, inv_prior_var, vis_response)
Apply LHS operator of linear system to an input vector.
Source code in hydra/sh_sampler.py
def apply_lhs_no_rot(a_cr, inv_noise_var, inv_prior_var, vis_response):
"""
Apply LHS operator of linear system to an input vector.
"""
real_noise_term = (
vis_response.real.T @ (inv_noise_var[:, np.newaxis] * vis_response.real) @ a_cr
)
imag_noise_term = (
vis_response.imag.T @ (inv_noise_var[:, np.newaxis] * vis_response.imag) @ a_cr
)
signal_term = inv_prior_var * a_cr
left_hand_side = real_noise_term + imag_noise_term + signal_term
return left_hand_side
apply_lhs_no_rot_mpi(comm, a_cr, inv_noise_var, inv_prior_var, vis_response)
Apply LHS operator of linear system to an input vector that has been split into chunks between MPI workers.
Source code in hydra/sh_sampler.py
def apply_lhs_no_rot_mpi(comm, a_cr, inv_noise_var, inv_prior_var, vis_response):
"""
Apply LHS operator of linear system to an input vector that has been
split into chunks between MPI workers.
"""
if comm is not None:
myid = comm.Get_rank()
else:
myid = 0
# Synchronise a_cr across all workers
if myid != 0:
a_cr *= 0.
if comm is not None:
comm.Bcast(a_cr, root=0)
# Calculate noise terms for this rank
my_tot_noise_term = (
vis_response.real.T
@ (inv_noise_var.flatten()[:, np.newaxis] * vis_response.real)
@ a_cr
+ vis_response.imag.T
@ (inv_noise_var.flatten()[:, np.newaxis] * vis_response.imag)
@ a_cr
)
# Do Reduce (sum) operation to get total operator on root node
tot_noise_term = np.zeros(
(1,), dtype=my_tot_noise_term.dtype
) # dummy data for non-root workers
if myid == 0:
tot_noise_term = np.zeros_like(my_tot_noise_term)
if comm is not None:
comm.Reduce(my_tot_noise_term, tot_noise_term, op=MPI_SUM, root=0)
else:
tot_noise_term = my_tot_noise_term
# Return result (only root worker has correct result)
if myid == 0:
signal_term = inv_prior_var * a_cr
return tot_noise_term + signal_term
else:
return np.zeros_like(a_cr)
construct_rhs_no_rot(data, inv_noise_var, inv_prior_var, omega_0, omega_1, a_0, vis_response)
Construct RHS of linear system.
Source code in hydra/sh_sampler.py
def construct_rhs_no_rot(
data, inv_noise_var, inv_prior_var, omega_0, omega_1, a_0, vis_response
):
"""
Construct RHS of linear system.
"""
real_data_term = vis_response.real.T @ (
inv_noise_var * data.real + np.sqrt(inv_noise_var) * omega_1.real
)
imag_data_term = vis_response.imag.T @ (
inv_noise_var * data.imag + np.sqrt(inv_noise_var) * omega_1.imag
)
prior_term = inv_prior_var * a_0 + np.sqrt(inv_prior_var) * omega_0
right_hand_side = real_data_term + imag_data_term + prior_term
return right_hand_side
construct_rhs_no_rot_mpi(comm, data, inv_noise_var, inv_prior_var, omega_a, omega_n, a_0, vis_response)
Construct RHS of linear system from data split across multiple MPI workers.
Source code in hydra/sh_sampler.py
def construct_rhs_no_rot_mpi(
comm, data, inv_noise_var, inv_prior_var, omega_a, omega_n, a_0, vis_response
):
"""
Construct RHS of linear system from data split across multiple MPI workers.
"""
if comm is not None:
myid = comm.Get_rank()
else:
myid = 0
# Synchronise omega_a across all workers
if myid != 0:
omega_a *= 0.
if comm is not None:
comm.Bcast(omega_a, root=0)
# Calculate data terms
my_data_term = vis_response.real.T @ (
(inv_noise_var * data.real).flatten()
+ np.sqrt(inv_noise_var).flatten() * omega_n.real.flatten()
) + vis_response.imag.T @ (
(inv_noise_var * data.imag).flatten()
+ np.sqrt(inv_noise_var).flatten() * omega_n.imag.flatten()
)
# Do Reduce (sum) operation to get total operator on root node
data_term = np.zeros(
(1,), dtype=my_data_term.dtype
) # dummy data for non-root workers
if myid == 0:
data_term = np.zeros_like(my_data_term)
if comm is not None:
comm.Reduce(my_data_term, data_term, op=MPI_SUM, root=0)
comm.barrier()
else:
data_term = my_data_term
# Return result (only root worker has correct result)
if myid == 0:
return data_term + inv_prior_var * a_0 + np.sqrt(inv_prior_var) * omega_a
else:
return np.zeros_like(a_0)
get_alms_from_gsm(freq, lmax, nside=64, resolution='low', output_model=False, output_map=False)
Generate a real array split as [real, imag] (without the m=0 modes imag-part) from gsm 2016 (https://github.com/telegraphic/pygdsm)
freqs (float or array_like): Frequency (in MHz) for which to return GSM model lmax (int): Maximum ell value for alms nside (int): The nside to upgrade/downgrade the map to. Default is nside=64. resolution (str): if "low/lo/l": nside = 64 (default) if "hi/high/h": nside = 1024 output_model (bool): If output_model=True: Outputs model generated from the GSM data. If output_model=False (default): no model output. output_map (bool): If output_map=True: Outputs map generated from the GSM data. If output_map=False (default): no map output.
Returns:
Name | Type | Description |
---|---|---|
alms |
array_like
|
Array of zeros except for the specified mode. The array represents all positive (+m) modes including zero and has double length, as real and imaginary values are split. The first half is the real values. |
gsm_2016 |
PyGDSM 2016 model
|
If output_model=True: Outputs model generated from the GSM data. If output_model=False (default): no model output. |
gsm_map |
healpy map
|
If output_map=True: Outputs map generated from the GSM data. If output_map=False (default): no map output. |
Source code in hydra/sh_sampler.py
def get_alms_from_gsm(
freq, lmax, nside=64, resolution="low", output_model=False, output_map=False
):
"""
Generate a real array split as [real, imag] (without the m=0 modes
imag-part) from gsm 2016 (https://github.com/telegraphic/pygdsm)
Parameters:
freqs (float or array_like):
Frequency (in MHz) for which to return GSM model
lmax (int):
Maximum ell value for alms
nside (int):
The nside to upgrade/downgrade the map to. Default is nside=64.
resolution (str):
if "low/lo/l": nside = 64 (default)
if "hi/high/h": nside = 1024
output_model (bool):
If output_model=True: Outputs model generated from the GSM data.
If output_model=False (default): no model output.
output_map (bool):
If output_map=True: Outputs map generated from the GSM data.
If output_map=False (default): no map output.
Returns:
alms (array_like):
Array of zeros except for the specified mode.
The array represents all positive (+m) modes including zero
and has double length, as real and imaginary values are split.
The first half is the real values.
gsm_2016 (PyGDSM 2016 model):
If output_model=True: Outputs model generated from the GSM data.
If output_model=False (default): no model output.
gsm_map (healpy map):
If output_map=True: Outputs map generated from the GSM data.
If output_map=False (default): no map output.
"""
return healpy2alms(
get_healpy_from_gsm(freq, lmax, nside, resolution, output_model, output_map)
)
get_em_ell_idx(lmax)
With (m,l) ordering!
Source code in hydra/sh_sampler.py
def get_em_ell_idx(lmax):
"""
With (m,l) ordering!
"""
ells_list = np.arange(0, lmax + 1)
em_real = np.arange(0, lmax + 1)
em_imag = np.arange(1, lmax + 1)
# ylabel = []
# First append all real (l,m) values
Nreal = 0
i = 0
idx = []
ems = []
ells = []
for em in em_real:
for ell in ells_list:
if ell >= em:
idx.append(i)
ems.append(em)
ells.append(ell)
Nreal += 1
i += 1
# Then all imaginary -- note: no m=0 modes!
Nimag = 0
for em in em_imag:
for ell in ells_list:
if ell >= em:
idx.append(i)
ems.append(em)
ells.append(ell)
Nimag += 1
i += 1
return ems, ells, idx
get_healpy_from_gsm(freq, lmax, nside=64, resolution='low', output_model=False, output_map=False)
Generate an array of alms (HEALpy ordered) from gsm 2016 (https://github.com/telegraphic/pygdsm)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
freqs
|
array_like
|
Frequency (in MHz) for which to return GSM model. |
required |
lmax
|
int
|
Maximum ell value for alms |
required |
nside
|
int
|
The nside to upgrade/downgrade the map to. Default is nside=64. |
64
|
resolution
|
str
|
if "low/lo/l": The GSM nside = 64 (default) if "hi/high/h": The GSM nside = 1024 |
'low'
|
output_model
|
bool
|
If output_model=True: Outputs model generated from the GSM data. If output_model=False (default): no model output. |
False
|
output_map
|
bool
|
If output_map=True: Outputs map generated from the GSM data. If output_map=False (default): no map output. |
False
|
Returns:
Name | Type | Description |
---|---|---|
healpy_modes |
array_like
|
Complex array of alms with same size and ordering as in healpy (m,l) |
gsm_2016 |
PyGDSM 2016 model
|
If output_model=True: Outputs model generated from the GSM data. If output_model=False (default): no model output. |
gsm_map |
healpy map
|
If output_map=True: Outputs map generated from the GSM data. If output_map=False (default): no map output. |
Source code in hydra/sh_sampler.py
def get_healpy_from_gsm(
freq, lmax, nside=64, resolution="low", output_model=False, output_map=False
):
"""
Generate an array of alms (HEALpy ordered) from gsm 2016
(https://github.com/telegraphic/pygdsm)
Parameters:
freqs (array_like):
Frequency (in MHz) for which to return GSM model.
lmax (int):
Maximum ell value for alms
nside (int):
The nside to upgrade/downgrade the map to. Default is nside=64.
resolution (str):
if "low/lo/l": The GSM nside = 64 (default)
if "hi/high/h": The GSM nside = 1024
output_model (bool):
If output_model=True: Outputs model generated from the GSM data.
If output_model=False (default): no model output.
output_map (bool):
If output_map=True: Outputs map generated from the GSM data.
If output_map=False (default): no map output.
Returns:
healpy_modes (array_like):
Complex array of alms with same size and ordering as in healpy (m,l)
gsm_2016 (PyGDSM 2016 model):
If output_model=True: Outputs model generated from the GSM data.
If output_model=False (default): no model output.
gsm_map (healpy map):
If output_map=True: Outputs map generated from the GSM data.
If output_map=False (default): no map output.
"""
# Instantiate GSM model and extract alms
try:
gsm_2016 = pygdsm.GlobalSkyModel2016(freq_unit="MHz", resolution=resolution)
except(AttributeError):
gsm_2016 = pygdsm.GlobalSkyModel16(freq_unit="MHz", resolution=resolution)
gsm_map = gsm_2016.generate(freqs=freq)
gsm_upgrade = hp.ud_grade(gsm_map, nside)
healpy_modes_gal = np.array([hp.map2alm(maps=_map, lmax=lmax) for _map in gsm_upgrade])
# By default it is in gal-coordinates, convert to equatorial
rot_gal2eq = hp.Rotator(coord="GC")
healpy_modes_eq = np.array([rot_gal2eq.rotate_alm(_modes) for _modes in healpy_modes_gal])
if output_model == False and output_map == False: # default
return healpy_modes_eq
elif output_model == False and output_map == True:
return healpy_modes_eq, gsm_map
elif output_model == True and output_map == False:
return healpy_modes_eq, gsm_2016
else:
return healpy_modes_eq, gsm_2016, gsm_map
healpy2alms(healpy_modes)
Takes a complex array of alms (positive modes only) and turns into a real array split as [real, imag] making sure to remove the m=0 modes from the imag-part.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
healpy_modes
|
(array_like, complex)
|
Array of zeros except for the specified mode. The array represents all positive (+m) modes including zeroth modes. |
required |
Returns:
Name | Type | Description |
---|---|---|
alms |
array_like
|
Array of zeros except for the specified mode. The array represents all positive (+m) modes including zero and is split into a real (first) and imag (second) part. The Imag part is smaller as the m=0 modes shouldn't contain and imaginary part. |
Source code in hydra/sh_sampler.py
def healpy2alms(healpy_modes):
"""
Takes a complex array of alms (positive modes only) and turns into
a real array split as [real, imag] making sure to remove the
m=0 modes from the imag-part.
Parameters:
healpy_modes (array_like, complex):
Array of zeros except for the specified mode.
The array represents all positive (+m) modes including zeroth modes.
Returns:
alms (array_like):
Array of zeros except for the specified mode.
The array represents all positive (+m) modes including zero
and is split into a real (first) and imag (second) part. The
Imag part is smaller as the m=0 modes shouldn't contain and
imaginary part.
"""
if len(healpy_modes.shape) == 1:
# Split healpy mode array into read and imaginary parts, with m=0
# imaginary modes excluded (since they are always zero for a real field)
lmax = hp.sphtfunc.Alm.getlmax(healpy_modes.size) # to remove the m=0 imag modes
alms = np.concatenate((healpy_modes.real, healpy_modes.imag[(lmax + 1) :]))
return alms
elif len(healpy_modes.shape) == 2:
# Loop through elements of the 2D input array (recursive)
return np.array([healpy2alms(_map) for _map in healpy_modes])
else:
raise ValueError("Input array must have shape (Nmodes,) or (Nmaps, Nmodes).")
sample_cl(alms, ell, m)
Sample C_ell from an inverse gamma distribution, given a set of SH coefficients. See Eq. 7 of Eriksen et al. (arXiv:0709.1058).
Source code in hydra/sh_sampler.py
def sample_cl(alms, ell, m):
"""
Sample C_ell from an inverse gamma distribution, given a set of
SH coefficients. See Eq. 7 of Eriksen et al. (arXiv:0709.1058).
"""
# Get m, ell ordering
m_vals, ell_vals, lm_idxs = get_em_ell_idx(lmax)
# Calculate sigma_ell = 1/(2 l + 1) sum_m |a_lm|^2
for ell in np.unique(ell_vals):
idxs = np.where(ell_vals == ell)
vis_proj_operator_no_rot(freqs, lsts, beams, ant_pos, lmax, nside, latitude=-0.5361913261514378, include_autos=False, autos_only=False, ref_freq=100.0, spectral_idx=0.0)
Precompute the real and imaginary blocks of the visibility response operator. This should only be done once and then "apply_vis_response()" is used to get the actual visibilities.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
freqs
|
array_like
|
Frequencies, in MHz. |
required |
lsts
|
array_like
|
LSTs (times) for the simulation. In radians. |
required |
beams
|
list of pyuvbeam
|
List of pyuveam objects, one for each antenna |
required |
ant_pos
|
dict
|
Dictionary of antenna positions, [x, y, z], in m. The keys should be the numerical antenna IDs. |
required |
lmax
|
int
|
Maximum ell value. Determines the number of modes used. |
required |
nside
|
int
|
Healpix nside to use for the calculation (longer baselines should use higher nside). |
required |
latitude
|
float
|
Latitude in decimal format of the simulated array/visibilities. |
-0.5361913261514378
|
include_autos
|
bool
|
If |
False
|
ref_freq
|
float
|
Reference frequency for the spectral dependence, in MHz. |
100.0
|
spectral_idx
|
float
|
Spectral index, |
0.0
|
Returns:
Name | Type | Description |
---|---|---|
vis_response_2D |
array_like
|
Visibility operator (δV_ij) for each (l,m) mode, frequency, baseline and lst. Shape (Nvis, Nalms) where Nvis is Nbl x Ntimes x Nfreqs. |
ell |
array of int
|
Array of ell-values for the visiblity simulation |
m |
array of int
|
Array of ell-values for the visiblity simulation |
Source code in hydra/sh_sampler.py
def vis_proj_operator_no_rot(
freqs,
lsts,
beams,
ant_pos,
lmax,
nside,
latitude=-0.5361913261514378,
include_autos=False,
autos_only=False,
ref_freq=100.0,
spectral_idx=0.0,
):
"""
Precompute the real and imaginary blocks of the visibility response
operator. This should only be done once and then "apply_vis_response()"
is used to get the actual visibilities.
Parameters:
freqs (array_like):
Frequencies, in MHz.
lsts (array_like):
LSTs (times) for the simulation. In radians.
beams (list of pyuvbeam):
List of pyuveam objects, one for each antenna
ant_pos (dict):
Dictionary of antenna positions, [x, y, z], in m. The keys should
be the numerical antenna IDs.
lmax (int):
Maximum ell value. Determines the number of modes used.
nside (int):
Healpix nside to use for the calculation (longer baselines should
use higher nside).
latitude (float):
Latitude in decimal format of the simulated array/visibilities.
include_autos (bool):
If `True`, the auto baselines are included.
ref_freq (float):
Reference frequency for the spectral dependence, in MHz.
spectral_idx (float):
Spectral index, `beta`, for the spectral dependence,
`~(freqs / ref_freq)^beta`.
Returns:
vis_response_2D (array_like):
Visibility operator (δV_ij) for each (l,m) mode, frequency,
baseline and lst. Shape (Nvis, Nalms) where Nvis is Nbl x Ntimes x Nfreqs.
ell (array of int):
Array of ell-values for the visiblity simulation
m (array of int):
Array of ell-values for the visiblity simulation
"""
ell, m, vis_alm = simulate_vis_per_alm(
lmax=lmax,
nside=nside,
ants=ant_pos,
freqs=freqs * 1e6, # MHz -> Hz
lsts=lsts,
beams=beams,
latitude=latitude,
)
# Removing visibility responses corresponding to the m=0 imaginary parts
vis_alm = np.concatenate(
(vis_alm[:, :, :, :, : len(ell)], vis_alm[:, :, :, :, len(ell) + (lmax + 1) :]),
axis=4,
)
ants = list(ant_pos.keys())
antpairs = []
if autos_only == False and include_autos == False:
auto_ants = []
for i in ants:
for j in ants:
# Toggle via keyword argument if you want to keep the auto baselines/only have autos
if include_autos == True:
if j >= i:
antpairs.append((ants[i], ants[j]))
elif autos_only == True:
if j == i:
antpairs.append((ants[i], ants[j]))
else:
if j == i:
auto_ants.append((ants[i], ants[j]))
if j > i:
antpairs.append((ants[i], ants[j]))
vis_response = np.zeros(
(len(antpairs), len(freqs), len(lsts), 2 * len(ell) - (lmax + 1)),
dtype=np.complex128,
)
## Collapse the two antenna dimensions into one baseline dimension
# Nfreqs, Ntimes, Nant1, Nant2, Nalms --> Nbl, Nfreqs, Ntimes, Nalms
for i, bl in enumerate(antpairs):
idx1 = ants.index(bl[0])
idx2 = ants.index(bl[1])
vis_response[i, :] = vis_alm[:, :, idx1, idx2, :]
# Multiply by spectral dependence model (a powerlaw)
# Shape: Nbl, Nfreqs, Ntimes, Nalms
vis_response *= ((freqs / ref_freq) ** spectral_idx)[
np.newaxis, :, np.newaxis, np.newaxis
]
# Reshape to 2D
# TODO: Make this into a "pack" and "unpack" function
# Nbl, Nfreqs, Ntimes, Nalms --> Nvis, Nalms
Nvis = len(antpairs) * len(freqs) * len(lsts)
vis_response_2D = vis_response.reshape(Nvis, 2 * len(ell) - (lmax + 1))
if autos_only == False and include_autos == False:
autos = np.zeros(
(len(auto_ants), len(freqs), len(lsts), 2 * len(ell) - (lmax + 1)),
dtype=np.complex128,
)
## Collapse the two antenna dimensions into one baseline dimension
# Nfreqs, Ntimes, Nant1, Nant2, Nalms --> Nbl, Nfreqs, Ntimes, Nalms
for i, bl in enumerate(auto_ants):
idx1 = ants.index(bl[0])
idx2 = ants.index(bl[1])
autos[i, :] = vis_alm[:, :, idx1, idx2, :]
## Reshape to 2D
## TODO: Make this into a "pack" and "unpack" function
# Nbl, Nfreqs, Ntimes, Nalms --> Nvis, Nalms
Nautos = len(auto_ants) * len(freqs) * len(lsts)
autos_2D = autos.reshape(Nautos, 2 * len(ell) - (lmax + 1))
return vis_response_2D, autos_2D, ell, m
else:
return vis_response_2D, ell, m