Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rx antenna pattern #432

Merged
merged 5 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
6 changes: 6 additions & 0 deletions docs/source/building_an_experiment.rst
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ rx_int_antennas *defaults*
rx_main_antennas *defaults*
The antennas to receive on in main array, default is all antennas given max number from config.

rx_antenna_pattern *defaults*
Experiment-defined function which returns a complex weighting factor of magnitude <= 1 for each
beam direction scanned in the experiment. The return value of the function must be an array of
size [beam_angle, antenna_num]. This function allows for custom beamforming of the receive
antennas for borealis processing of antenna iq to rawacf.

scanbound *defaults*
A list of seconds past the minute for averaging periods in a scan to align to. Defaults to None,
not required. If you set this, you will want to ensure that there is a slightly larger amount of
Expand Down
16 changes: 16 additions & 0 deletions docs/source/new_experiments.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ the first dimension of the returned array matches the first dimension of ``rx_be
slice dictionary, and that ``num_main_antennas`` matches the number of main antennas in the config
file.

Custom beamforming of the results measured during a full field of view experiment is possible
through the use of the defining the ``rx_antenna_pattern`` field in the full filed of view
Doreban marked this conversation as resolved.
Show resolved Hide resolved
experiment. A custom function can be written in the experiment and passed to borealis ::

beamforming_function(beam_angle, freq, antenna_count, antenna_spacing, offset=0.0):

...

slice_dict['tx_antenna_pattern'] = beamforming_function

The function should expect to receive beam angles, operating frequencies, number of antennas,
antenna spacing, and an offset. This function will be called for both the rx signals from the main
array and the interferometer array. The return is expected to be the desired phase for beamforming
each antenna, and should be of size [beam_angle, antenna_count]. The magnitude of each entry should
be less than or equal to 1.

.. _bistatic experiments:

--------------------
Expand Down
37 changes: 37 additions & 0 deletions src/experiment_prototype/experiment_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"rx_int_antennas",
"rx_main_antennas",
"rxonly",
"rx_antenna_pattern",
"scanbound",
"seqoffset",
"slice_id",
Expand Down Expand Up @@ -201,6 +202,11 @@ class ExperimentSlice:
from config.
rx_main_antennas *defaults*
The antennas to receive on in main array, default is all antennas given max number from config.
rx_antenna_pattern *defaults*
Experiment-defined function which returns a complex weighting factor of magnitude <= 1 for each
beam direction scanned in the experiment. The return value of the function must be an array of
size [beam_angle, antenna_num]. This function allows for custom beamforming of the receive
antennas for borealis processing of antenna iq to rawacf.
scanbound *defaults*
A list of seconds past the minute for averaging periods in a scan to align to. Defaults to None,
not required. If one slice in an experiment has a scanbound, they all must.
Expand Down Expand Up @@ -297,6 +303,7 @@ class ExperimentSlice:
max_items=options.intf_antenna_count,
unique_items=True)] = Field(default_factory=list)
tx_antenna_pattern: Optional[Callable] = default_callable
rx_antenna_pattern: Optional[Callable] = default_callable
tx_beam_order: Optional[beam_order_type] = Field(default_factory=list)
intt: Optional[confloat(ge=0)] = None
scanbound: Optional[list[confloat(ge=0)]] = Field(default_factory=list)
Expand Down Expand Up @@ -438,6 +445,36 @@ def check_tx_antenna_pattern(cls, tx_antenna_pattern, values):
f"values with a magnitude greater than 1")
return tx_antenna_pattern

@validator('rx_antenna_pattern')
def check_rx_antenna_pattern(cls, rx_antenna_pattern, values):
if rx_antenna_pattern is default_callable: # No value given
return

# Main and interferometer patterns
antenna_pattern = [rx_antenna_pattern(values['beam_angle'], values['freq'], options.main_antenna_count,
options.main_antenna_spacing),
rx_antenna_pattern(values['beam_angle'], values['freq'], options.intf_antenna_count,
options.intf_antenna_spacing, offset=-100)]
for index in range(0, len(antenna_pattern)):
if index == 0:
pattern = "main"
antenna_num = options.main_antenna_count
else:
pattern = "interferometer"
antenna_num = options.intf_antenna_count
if not isinstance(antenna_pattern[index], np.ndarray):
raise ValueError(f"Slice {values['slice_id']} {pattern} array rx antenna pattern return is "
f"not a numpy array")
else:
if antenna_pattern[index].shape != (len(values['beam_angle']), antenna_num):
raise ValueError(f"Slice {values['slice_id']} {pattern} array must be the same shape as"
f" ([beam angle], [antenna_count])")
antenna_pattern_mag = np.abs(antenna_pattern[index])
if np.argwhere(antenna_pattern_mag > 1.0).size > 0:
raise ValueError(f"Slice {values['slice_id']} {pattern} array rx antenna pattern return must not have "
f"any values with a magnitude greater than 1")
return rx_antenna_pattern

@validator('rx_beam_order', each_item=True)
def check_rx_beam_order(cls, rx_beam, values):
if 'beam_angle' in values:
Expand Down
15 changes: 11 additions & 4 deletions src/experiment_prototype/scan_classes/sequences.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,17 @@ def __init__(self, seqn_keys, sequence_slice_dict, sequence_interface, transmit_


# Now we set up the phases for receive side
rx_main_phase_shift = get_phase_shift(exp_slice.beam_angle, freq_khz, main_antenna_count,
main_antenna_spacing)
rx_intf_phase_shift = get_phase_shift(exp_slice.beam_angle, freq_khz, intf_antenna_count,
intf_antenna_spacing, intf_offset[0])
if exp_slice.rx_antenna_pattern is not None:
# Returns an array of size [beam_angle] of complex numbers of magnitude <= 1
rx_main_phase_shift = exp_slice.rx_antenna_pattern(exp_slice.beam_angle, freq_khz, main_antenna_count,
main_antenna_spacing)
rx_intf_phase_shift = exp_slice.rx_antenna_pattern(exp_slice.beam_angle, freq_khz, intf_antenna_count,
intf_antenna_spacing, intf_offset[0])
else:
rx_main_phase_shift = get_phase_shift(exp_slice.beam_angle, freq_khz, main_antenna_count,
main_antenna_spacing)
rx_intf_phase_shift = get_phase_shift(exp_slice.beam_angle, freq_khz, intf_antenna_count,
intf_antenna_spacing, intf_offset[0])

self.rx_beam_phases[slice_id] = {'main': rx_main_phase_shift, 'intf': rx_intf_phase_shift}

Expand Down