Source code for hermespy.beamforming.conventional

# -*- coding: utf-8 -*-

from __future__ import annotations
from typing_extensions import override

import numpy as np
from scipy.constants import pi, speed_of_light

from hermespy.core import AntennaArrayState, DeserializationProcess, Direction, SerializationProcess
from .beamformer import TransmitBeamformer, ReceiveBeamformer


__author__ = "Jan Adler"
__copyright__ = "Copyright 2024, Barkhausen Institut gGmbH"
__credits__ = ["Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.5.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"


[docs] class ConventionalBeamformer(TransmitBeamformer, ReceiveBeamformer): def __init__(self) -> None: # Initialize base classes TransmitBeamformer.__init__(self) ReceiveBeamformer.__init__(self)
[docs] def num_transmit_input_streams(self, num_output_streams: int) -> int: # The conventional beamformer distirbutes a single stream # to an arbitrary number of antenna streams return 1
[docs] def num_receive_output_streams(self, num_input_streams: int) -> int: # The convetional beamformer will always return a single stream, # combining all antenna signals into one return 1
@property def num_transmit_focus_points(self) -> int: # The conventional beamformer focuses a single angle return 1 @property def num_receive_focus_points(self) -> int: # The conventional beamformer focuses a single angle return 1 # @lru_cache(maxsize=2) def _codebook( self, carrier_frequency: float, angles: np.ndarray, array: AntennaArrayState ) -> np.ndarray: """Compute the beamforming codebook for a given set of angles of interest. Args: carrier_frequency (float): The assumed carrier central frequency of the samples. angles (numpy.ndarray): Spherical coordinate system angles of arrival in radians. A two dimensional numpy array with the first dimension representing the number of angles, and the second dimension of magnitude two containing the azimuth and zenith angle in radians, respectively. array (AntennaArray): The antenna array to compute the codebook for. Returns: The codebook represented by a two-dimensional numpy array, with the first dimension being the number of angles and the second dimension the number of antennas. """ # Query topology of receiving antenna ports topology = ( np.array([p.global_position for p in array.receive_ports], dtype=np.float64) - array.global_position ) # Build receive beamforming codebook of steering vectors for each angle of interest book = np.empty((angles.shape[0], array.num_receive_ports), dtype=complex) for n, (azimuth, zenith) in enumerate(angles): direction = Direction.From_Spherical(azimuth, zenith) weights = np.exp(-2j * pi * carrier_frequency / speed_of_light * (topology @ direction)) book[n, :] = weights return book / array.num_receive_ports
[docs] def _encode( self, samples: np.ndarray, carrier_frequency: float, focus_angles: np.ndarray, array: AntennaArrayState, ) -> np.ndarray: azimuth, zenith = focus_angles[0, :] # Compute conventional beamformer weights topology = ( np.array([p.global_position for p in array.transmit_ports], dtype=np.float64) - array.global_position ) direction = Direction.From_Spherical(azimuth, zenith) # Conventional steering vector weights = np.exp(-2j * pi * carrier_frequency / speed_of_light * (topology @ direction)) # Weight the streams accordingly samples = weights[:, np.newaxis] @ samples # That's it return samples
[docs] def _decode( self, samples: np.ndarray, carrier_frequency: float, angles: np.ndarray, array: AntennaArrayState, ) -> np.ndarray: codebook = self._codebook(carrier_frequency, angles[:, 0, :], array) beamformed_samples = codebook @ samples return beamformed_samples[:, np.newaxis, :]
[docs] @override def serialize(self, process: SerializationProcess) -> None: pass
[docs] @classmethod @override def Deserialize(cls, process: DeserializationProcess) -> ConventionalBeamformer: return ConventionalBeamformer()