"""Analytical far-field solver for antenna radiation patterns."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
import numpy as np
from complex_problems.antenna_radiation.model import (
antenna_pattern,
build_angular_grid,
compute_directivity,
compute_reaction_metrics,
degrees,
e_from_power_density,
estimate_aperture_lambda,
far_field_distance_min,
h_from_e_field,
normalize_pattern,
to_db10,
wavelength_from_frequency,
)
from utils import get_logger
logger = get_logger(__name__)
[docs]
@dataclass
class AntennaRadiationResult:
"""Result bundle for antenna radiation problem."""
antenna_type: str
theta: np.ndarray
phi: np.ndarray
pattern_norm: np.ndarray
directivity: np.ndarray
directivity_db: np.ndarray
gain: np.ndarray
gain_db: np.ndarray
power_density: np.ndarray
e_rms: np.ndarray
h_rms: np.ndarray
theta_cut_db: np.ndarray
phi_cut_db: np.ndarray
metadata: dict[str, Any] = field(default_factory=dict)
magnitudes: dict[str, float] = field(default_factory=dict)
[docs]
def solve_antenna_radiation(
*,
antenna_type: str = "dipole",
frequency_hz: float = 1.0e9,
transmit_power_w: float = 10.0,
efficiency: float = 0.9,
observation_distance_m: float = 50.0,
n_theta: int = 181,
n_phi: int = 360,
length_lambda: float = 0.5,
loop_radius_lambda: float = 0.1,
patch_length_lambda: float = 0.5,
patch_width_lambda: float = 0.4,
array_elements: int = 8,
array_spacing_lambda: float = 0.5,
array_phase_deg: float = 0.0,
array_steer_theta_deg: float = 90.0,
) -> AntennaRadiationResult:
"""Solve a far-field antenna radiation case."""
if frequency_hz <= 0.0:
raise ValueError("frequency_hz must be positive.")
if transmit_power_w <= 0.0:
raise ValueError("transmit_power_w must be positive.")
if not (0.0 < efficiency <= 1.0):
raise ValueError("efficiency must be in (0, 1].")
if observation_distance_m <= 0.0:
raise ValueError("observation_distance_m must be positive.")
theta, phi = build_angular_grid(n_theta=n_theta, n_phi=n_phi)
p_raw = antenna_pattern(
antenna_type=antenna_type,
theta=theta,
phi=phi,
length_lambda=length_lambda,
loop_radius_lambda=loop_radius_lambda,
patch_length_lambda=patch_length_lambda,
patch_width_lambda=patch_width_lambda,
array_elements=array_elements,
array_spacing_lambda=array_spacing_lambda,
array_phase_deg=array_phase_deg,
array_steer_theta_deg=array_steer_theta_deg,
)
p_norm = normalize_pattern(p_raw)
d = compute_directivity(p_norm, theta, phi)
g = efficiency * d
p_rad = transmit_power_w * efficiency
power_density = transmit_power_w * g / (4.0 * np.pi * observation_distance_m**2)
e_rms = e_from_power_density(power_density)
h_rms = h_from_e_field(e_rms)
d_db = to_db10(d)
g_db = to_db10(g)
# Principal cuts.
phi0_idx = 0
theta90_idx = int(np.argmin(np.abs(theta - 0.5 * np.pi)))
theta_cut_db = g_db[:, phi0_idx]
phi_cut_db = g_db[theta90_idx, :]
wavelength = wavelength_from_frequency(frequency_hz)
ap_lambda = estimate_aperture_lambda(
antenna_type=antenna_type,
length_lambda=length_lambda,
loop_radius_lambda=loop_radius_lambda,
patch_length_lambda=patch_length_lambda,
patch_width_lambda=patch_width_lambda,
array_elements=array_elements,
array_spacing_lambda=array_spacing_lambda,
)
r_ff = far_field_distance_min(wavelength, ap_lambda)
metrics = compute_reaction_metrics(
directivity=d,
gain=g,
theta=theta,
phi=phi,
p_norm=p_norm,
)
magnitudes = {
"directivity_max_db": metrics["directivity_max_db"],
"gain_max_db": metrics["gain_max_db"],
"beamwidth_deg": metrics["beamwidth_deg"],
"theta_peak_deg": metrics["theta_peak_deg"],
"phi_peak_deg": metrics["phi_peak_deg"],
"max_e_rms_vpm": float(np.max(e_rms)),
"far_field_min_m": float(r_ff),
}
metadata = {
"antenna_type": antenna_type.lower().strip(),
"frequency_hz": float(frequency_hz),
"wavelength_m": float(wavelength),
"transmit_power_w": float(transmit_power_w),
"radiated_power_w": float(p_rad),
"efficiency": float(efficiency),
"observation_distance_m": float(observation_distance_m),
"n_theta": int(n_theta),
"n_phi": int(n_phi),
"length_lambda": float(length_lambda),
"loop_radius_lambda": float(loop_radius_lambda),
"patch_length_lambda": float(patch_length_lambda),
"patch_width_lambda": float(patch_width_lambda),
"array_elements": int(array_elements),
"array_spacing_lambda": float(array_spacing_lambda),
"array_phase_deg": float(array_phase_deg),
"array_steer_theta_deg": float(array_steer_theta_deg),
"is_far_field": bool(observation_distance_m >= r_ff),
}
logger.info(
"Solved antenna radiation: type=%s, f=%g Hz, Dmax=%+.2f dB, Gmax=%+.2f dB",
metadata["antenna_type"],
frequency_hz,
magnitudes["directivity_max_db"],
magnitudes["gain_max_db"],
)
return AntennaRadiationResult(
antenna_type=metadata["antenna_type"],
theta=degrees(theta),
phi=degrees(phi),
pattern_norm=p_norm,
directivity=d,
directivity_db=d_db,
gain=g,
gain_db=g_db,
power_density=power_density,
e_rms=e_rms,
h_rms=h_rms,
theta_cut_db=theta_cut_db,
phi_cut_db=phi_cut_db,
metadata=metadata,
magnitudes=magnitudes,
)