Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cdd3519
add new beam evaluation functions that use 2 beams
tyler-a-cox Apr 3, 2026
53e233c
update multi-beam sims
tyler-a-cox Apr 15, 2026
14840c1
update type of beam_idx
tyler-a-cox Apr 15, 2026
f284a24
add beaminterface import to cpu_simulate
tyler-a-cox Apr 15, 2026
334aa87
properly place visibilities in correct location
tyler-a-cox Apr 15, 2026
c9be26d
make sure to copy beam so that results are not overwritten
tyler-a-cox Apr 15, 2026
62ab5ea
pass beam_idx properly
tyler-a-cox Apr 15, 2026
480f901
fix linux tests where _evaluate_vis_chunk is called directly
tyler-a-cox Apr 30, 2026
8a8a7dd
fix: send baselines to remote fnc
Apr 30, 2026
a4ecc88
add missing kwargs to evaluate_vis_chunk
tyler-a-cox Apr 30, 2026
b1a54ce
add new test that checks fftivs against matvis for per-antenna beams
tyler-a-cox Apr 30, 2026
0fbb68f
remove logic branch in BeamInterface
tyler-a-cox Apr 30, 2026
7868e33
restore BeamInterface branch
tyler-a-cox Apr 30, 2026
4b2955d
add source chunking to reduce total memory usage
tyler-a-cox May 1, 2026
2deb785
add chunking to the simulation
tyler-a-cox May 3, 2026
77f65fd
make some changes to reduce the memory
tyler-a-cox May 4, 2026
fbbad45
change source buffer arg
tyler-a-cox May 4, 2026
13f14b4
fix adjustments to beams in tests
tyler-a-cox May 4, 2026
effe7f6
fix tests for ubuntu
tyler-a-cox May 4, 2026
4a1eaa3
test wrapper errors with multiple beams
tyler-a-cox May 4, 2026
fe30b29
add better coverage to new beam methods
tyler-a-cox May 4, 2026
dbd772e
test failure modes in wrapper
tyler-a-cox May 4, 2026
8ac9529
address coverage of cpu/beams.py
tyler-a-cox May 4, 2026
ec87979
add documentation
tyler-a-cox May 4, 2026
769c994
Potential fix for pull request finding
tyler-a-cox May 4, 2026
a4914ff
Potential fix for pull request finding
tyler-a-cox May 4, 2026
2449bbd
Fix nbeams calculation for non-contiguous beam_idx in prepare_beam_ev…
Copilot May 4, 2026
6c3cf6d
Downgrade hot-loop logger.info calls to logger.debug in cpu_simulate.py
Copilot May 4, 2026
552cc7f
use min_chunks instead of nchunks
tyler-a-cox May 4, 2026
c136b98
get correct beam indicies
tyler-a-cox May 5, 2026
f5844db
update tests to reflect changes to prepare_beam_eval function
tyler-a-cox May 5, 2026
f773460
update tests
tyler-a-cox May 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 102 additions & 2 deletions src/fftvis/cpu/beams.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,40 @@ def evaluate_beam(
raise ValueError("Beam interpolation resulted in an invalid value")

return interp_beam

@staticmethod
def prepare_beam_evaluation(antnums, baselines, beam_idx):
"""
Compute the minimal flips for the given baselines and beam indices.
"""
if beam_idx is None:
return [(0, 0)], {(0, 0): np.arange(len(baselines))}, {(0, 0): [False] * len(baselines)}

# Get number of beams: use max index + 1 so non-contiguous indices
# (e.g. [0, 2, 2]) still produce the correct set of beam pairs.
nbeams = int(np.max(beam_idx)) + 1
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, this actually doesn't seem right to me. We should only include beam indices that actually exist, right? We don't need to use all pairs in the range from (0,maxidx)?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now building the unique beam pairs array with pairs actually used in beam_idx array


# Get the unique beam pairs that we need to evaluate the apparent flux for
unique_beam_pairs = [(bi, bj) for bi in range(nbeams) for bj in range(bi, nbeams)]

# Determine which baselines correspond to which beam pairs, and whether they need to be flipped
antnum_to_beam_idx = {ai: bidx for ai, bidx in zip(antnums, beam_idx)}
beam_pair_to_bls_idxs = {bp: [] for bp in unique_beam_pairs}
beam_pair_to_flipped = {bp: [] for bp in unique_beam_pairs}

for idx, (ai, aj) in enumerate(baselines):
bi, bj = antnum_to_beam_idx[ai], antnum_to_beam_idx[aj]
if (bi, bj) in unique_beam_pairs:
bp, flipped = (bi, bj), False
elif (bj, bi) in unique_beam_pairs:
bp, flipped = (bj, bi), True
else:
raise ValueError("Beam pair not in beam pair list")

beam_pair_to_bls_idxs[bp].append(idx)
beam_pair_to_flipped[bp].append(flipped)

return unique_beam_pairs, beam_pair_to_bls_idxs, beam_pair_to_flipped

@staticmethod
@nb.jit(nopython=True, parallel=False, nogil=False)
Expand All @@ -108,7 +142,7 @@ def get_apparent_flux_polarized_beam(beam: np.ndarray, flux: np.ndarray): # pra

@staticmethod
@nb.jit(nopython=True, parallel=False, nogil=False)
def get_apparent_flux_polarized(beam, coherency):
def get_apparent_flux_polarized(beam, coherency): # pragma: no cover
"""
Calculate the apparent flux of the sources using the beam and coherency matrices.

Expand Down Expand Up @@ -139,4 +173,70 @@ def get_apparent_flux_polarized(beam, coherency):
beam[0,0,i] = tmp00*a00 + tmp01*a10
beam[0,1,i] = tmp00*a01 + tmp01*a11
beam[1,0,i] = tmp10*a00 + tmp11*a10
beam[1,1,i] = tmp10*a01 + tmp11*a11
beam[1,1,i] = tmp10*a01 + tmp11*a11

@staticmethod
@nb.jit(nopython=True, parallel=False, nogil=False)
def get_apparent_flux_polarized_beam_pair(
beam_i: np.ndarray,
beam_j: np.ndarray,
flux: np.ndarray,
out: np.ndarray,
): # pragma: no cover
"""Compute A_i^H * diag(flux) * A_j for an unpolarized sky with two beams.

Generalises get_apparent_flux_polarized_beam to the case where the two
antennas forming a baseline have different beam patterns. When beam_i is
beam_j this gives the same result as the existing in-place method.

Unlike the existing method, i10 cannot be inferred from i01 by conjugation
since A_i^H A_j is not Hermitian in general, so all four elements are
computed explicitly.
"""
nax, nfd, nsrc = beam_i.shape
for isrc in range(nsrc):
ci = np.conj(beam_i[:, :, isrc]) # ci[a, p] = conj(A_i[a, p])

i00 = ci[0, 0] * beam_j[0, 0, isrc] + ci[1, 0] * beam_j[1, 0, isrc]
i01 = ci[0, 0] * beam_j[0, 1, isrc] + ci[1, 0] * beam_j[1, 1, isrc]
i10 = ci[0, 1] * beam_j[0, 0, isrc] + ci[1, 1] * beam_j[1, 0, isrc]
i11 = ci[0, 1] * beam_j[0, 1, isrc] + ci[1, 1] * beam_j[1, 1, isrc]

out[0, 0, isrc] = i00 * flux[isrc]
out[0, 1, isrc] = i01 * flux[isrc]
out[1, 0, isrc] = i10 * flux[isrc]
out[1, 1, isrc] = i11 * flux[isrc]


@staticmethod
@nb.jit(nopython=True, parallel=False, nogil=False)
def get_apparent_flux_polarized_pair(
beam_i: np.ndarray,
beam_j: np.ndarray,
coherency: np.ndarray,
out: np.ndarray,
): # pragma: no cover
"""Compute A_i^H @ C @ A_j for a polarized sky with two beams.

Generalises get_apparent_flux_polarized to the cross-beam case.
The left Jones matrix is A_i (conjugate transpose applied) and the right
is A_j, giving the measurement equation for a mixed-beam baseline.
"""
nsources = beam_i.shape[2]
for i in range(nsources):
ai00, ai01 = beam_i[0, 0, i], beam_i[0, 1, i]
ai10, ai11 = beam_i[1, 0, i], beam_i[1, 1, i]
aj00, aj01 = beam_j[0, 0, i], beam_j[0, 1, i]
aj10, aj11 = beam_j[1, 0, i], beam_j[1, 1, i]

# A_i^H @ C
tmp00 = np.conj(ai00) * coherency[0, 0, i] + np.conj(ai10) * coherency[1, 0, i]
tmp01 = np.conj(ai00) * coherency[0, 1, i] + np.conj(ai10) * coherency[1, 1, i]
tmp10 = np.conj(ai01) * coherency[0, 0, i] + np.conj(ai11) * coherency[1, 0, i]
tmp11 = np.conj(ai01) * coherency[0, 1, i] + np.conj(ai11) * coherency[1, 1, i]

# (A_i^H C) @ A_j
out[0, 0, i] = tmp00 * aj00 + tmp01 * aj10
out[0, 1, i] = tmp00 * aj01 + tmp01 * aj11
out[1, 0, i] = tmp10 * aj00 + tmp11 * aj10
out[1, 1, i] = tmp10 * aj01 + tmp11 * aj11
Loading
Loading