Source code for hermespy.beamforming.capon

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

from __future__ import annotations
from typing_extensions import override

import numpy as np

from hermespy.core import (
    AntennaMode,
    AntennaArrayState,
    DeserializationProcess,
    SerializationProcess,
)
from .beamformer import 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 CaponBeamformer(ReceiveBeamformer): def __init__(self, loading: float = 0.0) -> None: """ Args: loading: Diagonal covariance loading coefficient :math:`\\lambda`. Defaults to zero. """ self.loading = loading ReceiveBeamformer.__init__(self)
[docs] def num_receive_output_streams(self, num_input_streams: int) -> int: # The capon beaformer combies all input streams into a single output stream return 1
@property def num_receive_focus_points(self) -> int: return 1 @property def loading(self) -> float: """Magnitude of the diagonal sample covariance matrix loading. Required for robust matrix inversion in the case of rank-deficient sample covariances. Returns: Diagonal loading coefficient :math:`\\lambda`. Raises: ValueError: For loading coefficients smaller than zero. """ return self.__loading @loading.setter def loading(self, value: float) -> None: if value < 0.0: raise ValueError("Diagonal loading coefficient must be greater or equal to zero") self.__loading = value
[docs] def _decode( self, samples: np.ndarray, carrier_frequency: float, angles: np.ndarray, array: AntennaArrayState, ) -> np.ndarray: # Compute the inverse sample covariance matrix R # In order to avoid algebra exceptions on decodings without noise, we will resort to the pseudo-inverse, # which is able to invert rank-deficient matrices sample_covariance = np.linalg.inv( samples @ samples.T.conj() + self.loading * np.eye(samples.shape[0]) ) # Query the sensor array response vectors for the angles of interest and create a dictionary from it dictionary = np.empty((samples.shape[0], angles.shape[0]), dtype=complex) for d, focus in enumerate(angles): array_response = array.spherical_phase_response( carrier_frequency, focus[0, 0], focus[0, 1], AntennaMode.RX ) dictionary[:, d] = ( sample_covariance
[docs] @ array_response / (array_response.T.conj() @ sample_covariance @ array_response) ) beamformed_samples = dictionary.T.conj() @ samples return beamformed_samples[:, np.newaxis, :]
@override def serialize(self, process: SerializationProcess) -> None: process.serialize_floating(self.loading, "loading")
[docs] @classmethod @override def Deserialize(cls, process: DeserializationProcess) -> CaponBeamformer: return CaponBeamformer(process.deserialize_floating("loading"))