# -*- coding: utf-8 -*-
"""
==================
Impedance Coupling
==================
"""
from __future__ import annotations
from typing import Optional, TYPE_CHECKING
import numpy as np
from hermespy.core import Serializable, Signal
from .coupling import Coupling
if TYPE_CHECKING:
from hermespy.simulation import SimulatedDevice # pragma: no cover
__author__ = "Jan Adler"
__copyright__ = "Copyright 2023, Barkhausen Institut gGmbH"
__credits__ = ["Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.1.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"
[docs]
class ImpedanceCoupling(Serializable, Coupling):
"""Ideal mutual coupling between two antenna arrays."""
yaml_tag = "Impedance-Coupling"
__transmit_correlation: Optional[np.ndarray]
__receive_correlation: Optional[np.ndarray]
__transmit_impedance: Optional[np.ndarray]
__receive_impedance: Optional[np.ndarray]
__matching_impedance: Optional[np.ndarray]
def __init__(
self,
device: Optional[SimulatedDevice] = None,
transmit_correlation: Optional[np.ndarray] = None,
receive_correlation: Optional[np.ndarray] = None,
transmit_impedance: Optional[np.ndarray] = None,
receive_impedance: Optional[np.ndarray] = None,
matching_impedance: Optional[np.ndarray] = None,
) -> None:
"""
Args:
device (SimulatedDevice, optional)
Device the model is configured to.
transmit_correlation (np.ndarray, optional):
Correlation matrix of the transmit antenna array.
Defaults to the identity matrix.
receive_correlation (np.ndarray, optional):
Correlation matrix of the receive antenna array.
Defaults to the identity matrix.
transmit_impedance (np.ndarray, optional):
Impedance matrix of the transmit antenna array.
Defaults to the identity matrix.
receive_impedance (np.ndarray, optional):
Impedance matrix of the receive antenna array.
Defaults to the identity matrix.
matching_impedance (np.ndarray, optional):
Impedance matrix of the matching network.
Defaults to the identity matrix.
"""
Coupling.__init__(self, device=device)
self.transmit_correlation = transmit_correlation
self.receive_correlation = receive_correlation
self.transmit_impedance = transmit_impedance
self.receive_impedance = receive_impedance
self.matching_impedance = matching_impedance
@property
def transmit_correlation(self) -> Optional[np.ndarray]:
return self.__transmit_correlation
@transmit_correlation.setter
def transmit_correlation(self, value: Optional[np.ndarray]) -> None:
if value is None:
self.__transmit_correlation = None
return
if value.ndim != 2:
raise ValueError("Transmit correlation must be a two dimensional array")
if value.shape[0] != value.shape[1]:
raise ValueError("Transmit correlation must be square")
self.__transmit_correlation = value
@property
def receive_correlation(self) -> np.ndarray:
return self.__receive_correlation
@receive_correlation.setter
def receive_correlation(self, value: Optional[np.ndarray]) -> None:
if value is None:
self.__receive_correlation = None
return
if value.ndim != 2:
raise ValueError("Receive correlation must be a two dimensional array")
if value.shape[0] != value.shape[1]:
raise ValueError("Receive correlation must be square")
self.__receive_correlation = value
@property
def transmit_impedance(self) -> Optional[np.ndarray]:
return self.__transmit_impedance
@transmit_impedance.setter
def transmit_impedance(self, value: Optional[np.ndarray]) -> None:
if value is None:
self.__transmit_impedance = None
return
if value.ndim != 2:
raise ValueError("Transmit impedance must be a two dimensional array")
if value.shape[0] != value.shape[1]:
raise ValueError("Transmit impedance must be square")
self.__transmit_impedance = value
@property
def receive_impedance(self) -> Optional[np.ndarray]:
return self.__receive_impedance
@receive_impedance.setter
def receive_impedance(self, value: Optional[np.ndarray]) -> None:
if value is None:
self.__receive_impedance = None
return
if value.ndim != 2:
raise ValueError("Receive impedance must be a two dimensional array")
if value.shape[0] != value.shape[1]:
raise ValueError("Receive impedance must be square")
self.__receive_impedance = value
@property
def matching_impedance(self) -> Optional[np.ndarray]:
return self.__matching_impedance
@matching_impedance.setter
def matching_impedance(self, value: Optional[np.ndarray]) -> None:
if value is None:
self.__matching_impedance = None
return
if value.ndim != 2:
raise ValueError("Matching impedances must be a two dimensional array")
if value.shape[0] != value.shape[1]:
raise ValueError("Matching impedances must be square")
self.__matching_impedance = value
def _transmit(self, signal: Signal) -> Signal:
transmit_impedance = (
np.eye(self.device.antennas.num_transmit_antennas)
if self.transmit_impedance is None
else self.transmit_impedance
)
transmit_correlation = (
np.eye(self.device.antennas.num_transmit_antennas)
if self.transmit_correlation is None
else self.transmit_correlation
)
transmit_coupling = transmit_impedance.real**-0.5 @ transmit_correlation**0.5
transmitted_samples = transmit_coupling @ signal.samples
return Signal(transmitted_samples, signal.sampling_rate, signal.carrier_frequency)
def _receive(self, signal: Signal) -> Signal:
receive_impedance = (
np.eye(self.device.antennas.num_receive_antennas)
if self.receive_impedance is None
else self.receive_impedance
)
receive_correlation = (
np.eye(self.device.antennas.num_receive_antennas)
if self.receive_correlation is None
else self.receive_correlation
)
matching_impedance = (
np.eye(self.device.antennas.num_receive_antennas)
if self.matching_impedance is None
else self.matching_impedance
)
receive_coupling = (
2
* receive_impedance[0, 0].real
* matching_impedance.real**0.5
@ np.linalg.inv(matching_impedance + receive_correlation)
@ receive_correlation**0.5
)
received_samples = receive_coupling @ signal.samples
return Signal(received_samples, signal.sampling_rate, signal.carrier_frequency)