"""Post-processing data and figures of merit for antennas, including radiation efficiency,
reflection efficiency, gain, and realized gain.
"""
from __future__ import annotations
from typing import Optional
import pydantic.v1 as pd
import xarray as xr
from tidy3d.components.data.data_array import FieldProjectionAngleDataArray, FreqDataArray
from tidy3d.components.data.monitor_data import DirectivityData
from tidy3d.components.types import PolarizationBasis
from tidy3d.log import log
[docs]
class AntennaMetricsData(DirectivityData):
"""Data representing the main parameters and figures of merit for antennas.
Example
-------
>>> import numpy as np
>>> from tidy3d.components.data.monitor_data import FluxDataArray, FieldProjectionAngleDataArray
>>> from tidy3d.components.monitor import DirectivityMonitor
>>> f = np.linspace(1e14, 2e14, 10)
>>> r = np.atleast_1d(1e6)
>>> theta = np.linspace(0, np.pi, 10)
>>> phi = np.linspace(0, 2*np.pi, 20)
>>> coords = dict(r=r, theta=theta, phi=phi, f=f)
>>> coords_flux = dict(f=f)
>>> field_values = (1+1j) * np.random.random((len(r), len(theta), len(phi), len(f)))
>>> flux_data = FluxDataArray(np.random.random(len(f)), coords=coords_flux)
>>> scalar_field = FieldProjectionAngleDataArray(field_values, coords=coords)
>>> monitor = DirectivityMonitor(
... center=(1,2,3),
... size=(2,2,2),
... freqs=f,
... name="rad_monitor",
... phi=phi,
... theta=theta
... ) # doctest: +SKIP
>>> power_data = FreqDataArray(np.random.random(len(f)), coords=coords_flux) # doctest: +SKIP
>>> data = AntennaMetricsData(
... monitor=monitor,
... projection_surfaces=monitor.projection_surfaces,
... flux=flux_data,
... Er=scalar_field,
... Etheta=scalar_field,
... Ephi=scalar_field,
... Hr=scalar_field,
... Htheta=scalar_field,
... Hphi=scalar_field,
... power_incident=power_data,
... power_reflected=power_data
... ) # doctest: +SKIP
Notes
-----
The definitions of radiation efficiency, reflection efficiency, gain, and realized gain
are based on:
Balanis, Constantine A., "Antenna Theory: Analysis and Design,"
John Wiley & Sons, Chapter 2.9 (2016).
"""
power_incident: FreqDataArray = pd.Field(
...,
title="Power incident",
description="Array of values representing the incident power to an antenna.",
)
power_reflected: FreqDataArray = pd.Field(
...,
title="Power reflected",
description="Array of values representing power reflected due to an impedance mismatch with the antenna.",
)
[docs]
@staticmethod
def from_directivity_data(
dir_data: DirectivityData, power_inc: FreqDataArray, power_refl: FreqDataArray
) -> AntennaMetricsData:
"""Create :class:`.AntennaMetricsData` from directivity data and power measurements.
Parameters
----------
dir_data : :class:`.DirectivityData`
Directivity data containing field components and flux measurements.
power_inc : :class:`.FreqDataArray`
Array of values representing the incident power to an antenna.
power_refl : :class:`.FreqDataArray`
Array of values representing power reflected due to impedance mismatch with the antenna.
Returns
-------
:class:`.AntennaMetricsData`
New instance combining directivity data with incident and reflected power measurements.
"""
antenna_params_dict = {
**dir_data.dict(),
"power_incident": power_inc,
"power_reflected": power_refl,
}
antenna_params_dict.pop("type")
return AntennaMetricsData(**antenna_params_dict)
@property
def supplied_power(self) -> FreqDataArray:
"""The power supplied to the antenna, which takes into account reflections."""
return self.power_incident - self.power_reflected
@property
def radiation_efficiency(self) -> FreqDataArray:
"""The radiation efficiency of the antenna."""
return self.calc_radiation_efficiency(self.supplied_power)
@property
def reflection_efficiency(self) -> FreqDataArray:
"""The reflection efficiency of the antenna, which is due to an impedance mismatch."""
reflection_efficiency = self.supplied_power / self.power_incident
return reflection_efficiency
[docs]
def partial_gain(
self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None
) -> xr.Dataset:
"""The partial gain figures of merit for antennas. The partial gains are computed
in the ``linear`` or ``circular`` polarization bases. If ``tilt_angle`` is not ``None``,
the partial directivity is computed in the linear polarization basis rotated by ``tilt_angle``
from the theta-axis. Gain is dimensionless.
Parameters
----------
pol_basis : PolarizationBasis
The desired polarization basis used to express partial gain, either
``linear`` or ``circular``.
tilt_angle : float
The angle by which the co-polar vector is rotated from the theta-axis.
At ``tilt_angle`` = 0, the co-polar vector coincides with the theta-axis and the cross-polar
vector coincides with the phi-axis; while at ``tilt_angle = pi/2``, the co-polar vector
coincides with the phi-axis.
Returns
-------
``xarray.Dataset``
Dataset containing the partial gains split into the two polarization states.
"""
self._check_valid_pol_basis(pol_basis, tilt_angle)
partial_D = self.partial_directivity(pol_basis=pol_basis, tilt_angle=tilt_angle)
if pol_basis == "linear":
if tilt_angle is None:
rename_mapping = {"Dtheta": "Gtheta", "Dphi": "Gphi"}
else:
rename_mapping = {"Dco": "Gco", "Dcross": "Gcross"}
else:
rename_mapping = {"Dright": "Gright", "Dleft": "Gleft"}
return self.radiation_efficiency * partial_D.rename(rename_mapping)
@property
def gain(self) -> FieldProjectionAngleDataArray:
"""The gain figure of merit for antennas. Gain is dimensionless."""
partial_G = self.partial_gain()
return partial_G.Gtheta + partial_G.Gphi
[docs]
def partial_realized_gain(
self, pol_basis: PolarizationBasis = "linear", tilt_angle: Optional[float] = None
) -> xr.Dataset:
"""The partial realized gain figures of merit for antennas. The partial gains are computed
in the ``linear`` or ``circular`` polarization bases. If ``tilt_angle`` is not ``None``,
the partial directivity is computed in the linear polarization basis rotated by ``tilt_angle``
from the theta-axis. Gain is dimensionless.
Parameters
----------
pol_basis : PolarizationBasis
The desired polarization basis used to express partial gain, either
``linear`` or ``circular``.
tilt_angle : float
The angle by which the co-polar vector is rotated from the theta-axis.
At ``tilt_angle`` = 0, the co-polar vector coincides with the theta-axis and the cross-polar
vector coincides with the phi-axis; while at ``tilt_angle = pi/2``, the co-polar vector
coincides with the phi-axis.
Returns
-------
``xarray.Dataset``
Dataset containing the partial realized gains split into the two polarization states.
"""
self._check_valid_pol_basis(pol_basis, tilt_angle)
reflection_efficiency = self.reflection_efficiency
partial_G = self.partial_gain(pol_basis=pol_basis, tilt_angle=tilt_angle)
return reflection_efficiency * partial_G
@property
def realized_gain(self) -> FieldProjectionAngleDataArray:
"""The realized gain figure of merit for antennas. Realized gain is dimensionless."""
partial_G = self.partial_realized_gain()
return partial_G.Gtheta + partial_G.Gphi
@pd.root_validator(pre=False)
def _warn_rf_license(cls, values):
log.warning(
"โน๏ธ โ ๏ธ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.",
log_once=True,
)
return values