Source code for comyx.network.ris

from __future__ import annotations

from typing import Any, List, Union

import numpy as np
import numpy.typing as npt

NDArrayFloat = npt.NDArray[np.floating[Any]]
RVDistribution = Any
NDArrayComplex = npt.NDArray[np.complexfloating[Any, Any]]


[docs] class RIS: r"""Represents a reconfigurable intelligent surface (RIS). An RIS is a surface with a large number of elements that can be electronically controlled to reflect the incoming signal in a desired direction. Mathematically, the reflection matrix of the RIS is given by .. math:: \mathbf{R} = \text{diag}(\mathbf{a} \odot \exp(j \mathbf{\Phi})), where :math:`\mathbf{a}` is the vector of amplitudes, :math:`\mathbf{\Phi}` is the vector of phase shifts, and :math:`\odot` is the Hadamard product. """ def __init__( self, id_: str, n_elements: int, position: Union[List[float], None] = None, ): """Initialize an RIS object. Args: id_: Unique identifier of the RIS. n_elements: Number of elements of the RIS. position: Position of the RIS in the environment. """ self._id = id_ self._position = position self._n_elements = n_elements @property def id(self) -> str: """Return the unique identifier of the RIS.""" return self._id @property def position(self) -> List[float]: """Return the position of the RIS in the environment.""" return self._position @position.setter def position(self, position: List[float]) -> None: """Set the position of the RIS in the environment.""" self._position = position @property def n_elements(self) -> int: """Return the number of elements of the RIS.""" return self._n_elements @property def phase_shifts(self) -> NDArrayFloat: """Return the phase shifts of the RIS.""" return self._get_attribute("_phase_shifts") @phase_shifts.setter def phase_shifts(self, phase_shifts: NDArrayFloat) -> None: """Set the phase shifts of the RIS.""" self._set_attribute("_phase_shifts", phase_shifts) @property def amplitudes(self) -> NDArrayFloat: """Return the amplitudes of the RIS.""" return self._get_attribute("_amplitudes") @amplitudes.setter def amplitudes(self, amplitudes: NDArrayFloat) -> None: """Set the amplitudes of the RIS.""" self._set_attribute("_amplitudes", amplitudes) @property def reflection_matrix(self) -> NDArrayComplex: """Return the reflection matrix of the RIS. The reflection matrix is a diagonal matrix with the phase shifts and amplitudes as its diagonal elements. The phase shifts and amplitudes must be set before accessing the reflection matrix. The diagonality of the reflection matrix is due to the fact that the each element of the RIS reflects the incoming signal independently of the other elements. Returns: The reflection matrix of the RIS. """ if not hasattr(self, "_phase_shifts"): raise ValueError("Phase shifts must be set before accessing.") if not hasattr(self, "_amplitudes"): raise ValueError("Amplitudes must be set before accessing.") assert self.phase_shifts.ndim == 1, ( "Phase shifts must be a vector (design choice)." + " Use amplitude and shifts individually instead.", ) return np.diag(self.amplitudes * np.exp(1j * self.phase_shifts)) def __repr__(self) -> str: return f"{self.id}(position={self.position}, n_elements={self.n_elements})" def _get_attribute(self, attr: str) -> NDArrayFloat: """Return the attribute of the RIS.""" if not hasattr(self, attr): raise ValueError(f"{attr[1:]} must be set before accessing.") return getattr(self, attr) def _set_attribute(self, attr: str, value: NDArrayFloat) -> None: """Set the attribute of the RIS.""" assert ( value.shape[0] == self.n_elements ), f"{attr[1:]} must be a vector of length equal to the number of elements." setattr(self, attr, value)
[docs] class STAR_RIS: r"""Represents a STAR-RIS (Simultaneously Transmitting and Reflecting RIS). An STAR-RIS is a surface with a large number of elements that can be electronically controlled to reflect and transmit the incoming signal in a both spaces. Mathematically, the characteristic matrices of the STAR-RIS is given by .. math:: \mathbf{R}^{n} = \text{diag}(\mathbf{a}^{n} \odot \exp(j \mathbf{\Phi}^{n})), where :math:`n \in \{t, r\}` and represents the transmission and reflection mode, respectively. Furthermore, $\mathbf{a}^{n}$ is the vector of amplitudes, :math:`\mathbf{\Phi}^{n}` is the vector of phase shifts, and $\odot$ is the Hadamard product. Note that STAR-RIS must satisfy the law of conservation of energy, that is, :math:`\forall i \in \{1, \ldots, N\}` (where :math:`N` is the number of elements of the STAR-RIS), :math:`(a^{t}_{i})^2 + (a^{r}_{i})^2 = 1`. """ def __init__( self, id_: str, n_elements: int, position: Union[List[float], None] = None, ): """Initialize a STAR-RIS object. Args: id_: Unique identifier of the STAR-RIS. n_elements: Number of both transmission and reflection elements of the STAR-RIS. position: Position of the STAR-RIS in the environment. """ self._id = id_ self._position = position self._n_elements = n_elements @property def id(self) -> str: """Return the unique identifier of the STAR-RIS.""" return self._id @property def position(self) -> List[float]: """Return the position of the STAR-RIS in the environment.""" return self._position @position.setter def position(self, position: List[float]) -> None: """Set the position of the STAR-RIS in the environment.""" self._position = position @property def n_elements(self) -> int: """Return the number of antennas of the STAR-RIS.""" return self._n_elements @property def reflection_phases(self) -> NDArrayFloat: """Return the reflection phase shifts of the STAR-RIS.""" return self._get_attribute("_reflection_phases") @reflection_phases.setter def reflection_phases(self, reflection_phases: NDArrayFloat) -> None: """Set the reflection phase shifts of the STAR-RIS.""" self._set_attribute("_reflection_phases", reflection_phases) @property def transmission_phases(self) -> NDArrayFloat: """Return the transmission phase shifts of the STAR-RIS.""" return self._get_attribute("_transmission_phases") @transmission_phases.setter def transmission_phases(self, transmission_phases: NDArrayFloat) -> None: """Set the transmission phase shifts of the STAR-RIS.""" self._set_attribute("_transmission_phases", transmission_phases) @property def reflection_amplitudes(self) -> NDArrayFloat: """Return the reflection amplitudes of the STAR-RIS.""" return self._get_attribute("_reflection_amplitudes") @reflection_amplitudes.setter def reflection_amplitudes(self, reflection_amplitudes: NDArrayFloat) -> None: """Set the reflection amplitudes of the STAR-RIS.""" self._set_attribute("_reflection_amplitudes", reflection_amplitudes) @property def transmission_amplitudes(self) -> NDArrayFloat: """Return the transmission amplitudes of the STAR-RIS.""" return self._get_attribute("_transmission_amplitudes") @transmission_amplitudes.setter def transmission_amplitudes(self, transmission_amplitudes: NDArrayFloat) -> None: """Set the transmission amplitudes of the STAR-RIS.""" self._set_attribute("_transmission_amplitudes", transmission_amplitudes) @property def reflection_matrix(self) -> NDArrayComplex: """Return the reflection matrix of the STAR-RIS. The reflection matrix is a diagonal matrix with the phase shifts and amplitudes as its diagonal elements. The phase shifts and amplitudes must be set before accessing the reflection matrix. The diagonality of the reflection matrix is due to the fact that the each element of the RIS reflects the incoming signal independently of the other elements. Returns: The reflection matrix of the RIS. """ if not hasattr(self, "_reflection_shifts"): raise ValueError("Reflection phase shifts must be set before accessing.") if not hasattr(self, "_reflection_amplitudes"): raise ValueError("Reflection amplitudes must be set before accessing.") assert self.reflection_shifts.ndim == 1, ( "Reflection phase shifts must be a vector (design choice)." + " Use amplitude and shifts individually instead.", ) return np.diag(self.reflection_amplitudes * np.exp(1j * self.reflection_shifts)) @property def transmission_matrix(self) -> NDArrayComplex: """Return the transmission matrix of the STAR-RIS. The transmission matrix is a diagonal matrix with the phase shifts and amplitudes as its diagonal elements. The phase shifts and amplitudes must be set before accessing the transmission matrix. The diagonality of the transmission matrix is due to the fact that the each element of the RIS reflects the incoming signal independently of the other elements. Returns: The transmission matrix of the STAR-RIS. """ if not hasattr(self, "_transmission_shifts"): raise ValueError("Transmission phase shifts must be set before accessing.") if not hasattr(self, "_transmission_amplitudes"): raise ValueError("Transmission amplitudes must be set before accessing.") assert self.transmission_shifts.ndim == 1, ( "Transmission phase shifts must be a vector (design choice)." + " Use amplitude and shifts individually instead.", ) return np.diag( self.transmission_amplitudes * np.exp(1j * self.transmission_shifts) ) def _get_attribute(self, attr: str) -> NDArrayFloat: """Return the attribute of the STAR-RIS.""" if not hasattr(self, attr): raise ValueError(f"{attr[1:]} must be set before accessing.") return getattr(self, attr) def _set_attribute(self, attr: str, value: NDArrayFloat) -> None: """Set the attribute of the STAR-RIS.""" assert ( value.shape[0] == self.n_elements ), f"{attr[1:]} must be a vector of length equal to the number of elements." setattr(self, attr, value) def __repr__(self) -> str: return f"{self.id}(position={self.position}, n_elements={self.n_elements})"
__all__ = ["RIS", "STAR_RIS"]