Source code for kaira.constraints.antenna

"""Antenna-specific constraints for communication systems.

This module provides constraints that apply to multi-antenna systems such as MIMO, including per-
antenna power distribution and other antenna-specific limitations. These constraints are essential
for ensuring proper operation of multi-antenna transmitters and compliance with hardware
specifications :cite:`paulraj2003introduction` :cite:`spencer2004introduction`.
"""

from typing import Optional

import torch

from .base import BaseConstraint
from .registry import ConstraintRegistry


[docs] @ConstraintRegistry.register_constraint() class PerAntennaPowerConstraint(BaseConstraint): """Distributes power budget across multiple antennas to ensure per-antenna power limits. Ensures each antenna in a multi-antenna system (such as MIMO) adheres to its specific power budget. This constraint is crucial for systems where each antenna has its own power amplifier with individual power limitations :cite:`yu2007transmitter` :cite:`wunder2013energy`. Per-antenna power constraints are often more practical than sum-power constraints in real MIMO systems, as discussed in :cite:`christopoulos2014weighted` and :cite:`yu2007transmitter`. The constraint can be configured either with individual power budgets for each antenna or with a uniform power value across all antennas. Attributes: power_budget (torch.Tensor, optional): Power budget tensor for each antenna uniform_power (float, optional): Uniform power level for all antennas """
[docs] def __init__(self, power_budget: Optional[torch.Tensor] = None, uniform_power: Optional[float] = None, *args, **kwargs) -> None: """Initialize the per-antenna power constraint. Args: power_budget (torch.Tensor, optional): Power budget for each antenna. Shape should be [num_antennas]. Mutually exclusive with uniform_power. uniform_power (float, optional): Uniform power value to apply across all antennas. Mutually exclusive with power_budget. *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. Raises: AssertionError: If neither power_budget nor uniform_power is provided Note: Either power_budget or uniform_power must be provided, but not both. If power_budget is provided, its length must match the number of antennas in the input signal. """ super().__init__(*args, **kwargs) assert (power_budget is not None) or (uniform_power is not None), "Either power_budget or uniform_power must be provided" self.power_budget = power_budget self.uniform_power = uniform_power
[docs] def forward(self, x: torch.Tensor, *args, **kwargs) -> torch.Tensor: """Apply per-antenna power constraint. Scales the signal from each antenna independently to meet its power budget. The second dimension of the input tensor is assumed to be the antenna dimension. Args: x (torch.Tensor): Input tensor with shape [batch_size, num_antennas, ...]. The second dimension must correspond to different antennas. *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. Returns: torch.Tensor: Power-constrained signal with the same shape as input, where each antenna's signal has been scaled to meet its power budget Note: Power is calculated by averaging the squared magnitude across all dimensions except batch and antenna dimensions. """ # Calculate current power per antenna (all dimensions except batch and antenna) spatial_dims = tuple(range(2, len(x.shape))) antenna_power = torch.mean(torch.abs(x) ** 2, dim=spatial_dims, keepdim=True) # Determine target power if self.power_budget is not None: target_power = self.power_budget.view(1, -1, *([1] * (len(x.shape) - 2))) else: # Use uniform power target_power = self.uniform_power * torch.ones_like(antenna_power) # Scale to meet power constraints scaling_factor = torch.sqrt(target_power / (antenna_power + 1e-8)) return x * scaling_factor