Source code for hermespy.beamforming.nullsteeringbeamformer

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

import numpy as np
from scipy.linalg import pinvh

from hermespy.beamforming import TransmitBeamformer, ReceiveBeamformer
from hermespy.core import AntennaArrayState

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


# Define the NullStearingBeamformer class by inheriting from the ReceiveBeamformer class
[docs] class NullSteeringBeamformer(TransmitBeamformer, ReceiveBeamformer): def __init__(self) -> None: TransmitBeamformer.__init__(self) ReceiveBeamformer.__init__(self)
[docs] def num_transmit_input_streams(self, num_output_streams: int) -> int: # The null steering 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 null steering beamformer will always return a single stream, # combining all antenna signals into one return 1
@property def num_transmit_focus_points(self) -> int: # The null steering beamformer focuses a single direction, # while steering the nulls in two other directions return 3 @property def num_receive_focus_points(self) -> int: # The null steering beamformer focuses a single direction, # while steering the nulls in two other directions return 3 # calculate the null steering beamformer weights def _weights( self, carrier_frequency: float, focus_angles: np.ndarray, array: AntennaArrayState ) -> np.ndarray: a0 = array.spherical_phase_response( carrier_frequency, focus_angles[0, 0], focus_angles[0, 1] ) a1 = array.spherical_phase_response( carrier_frequency, focus_angles[1, 0], focus_angles[1, 1] ) a2 = array.spherical_phase_response( carrier_frequency, focus_angles[2, 0], focus_angles[2, 1] ) A = np.array([a1, a2]).T PA = A.conj() @ pinvh(A.T @ A.conj(), check_finite=False) @ A.T Identity_Matrix = np.eye(PA.shape[0]) wns = (Identity_Matrix - PA) @ a0 wns /= np.linalg.norm(wns) return wns
[docs] def _encode( self, samples: np.ndarray, carrier_frequency: float, focus_angles: np.ndarray, array: AntennaArrayState, ) -> np.ndarray: # Compute nullsteering beamformer weights weights = self._weights(carrier_frequency, focus_angles, array) # Weight the streams accordingly samples = weights[:, np.newaxis] @ samples return samples
[docs] def _decode( self, samples: np.ndarray, carrier_frequency: float, angles: np.ndarray, array: AntennaArrayState, ) -> np.ndarray: # Query the sensor array response vectors for the angles of interest and create a dictionary from it which contains the beamforming weights dictionary = np.empty((array.num_receive_antennas, angles.shape[0]), dtype=complex) for d, focus in enumerate(angles): dictionary[:, d] = self._weights(carrier_frequency, focus, array) beamformed_samples = dictionary.T @ samples return beamformed_samples[:, np.newaxis, :]