Antenna Radiation Pattern
Compute the radiation pattern of a dipole antenna using near-to-far field transformation
Antenna Radiation Pattern
This example computes the radiation pattern of a dipole antenna and compares the Meep near-to-far field result with analytic theory.
Key Feature: Demonstrates the near-to-far field (N2F) transformation for computing radiation patterns from near-field FDTD data.
Physics Background
Dipole Radiation Pattern
A short electric dipole oriented along the x-axis has an angular radiation pattern:
$$ P(\theta) \propto \sin^2(\theta) $$
where θ is the angle from the dipole axis. The radiation is maximum perpendicular to the dipole and zero along its axis.
Near-to-Far Field Transformation
The N2F transformation uses the equivalence principle to compute far-field radiation from near-field data:
- Record tangential E and H fields on a closed surface
- Use surface currents as equivalent sources
- Compute far-field radiation integral
Simulation Overview
Create Dipole Source
Place a point dipole source (Ex, Ey, or Ez component) at the origin.
Define Near-Field Box
Set up a flux box around the source to record near-field data.
Run Simulation
Run until fields decay to extract steady-state frequency-domain data.
Compute Far-Field
Use N2F transformation to calculate radiation pattern at far-field radius.
Compare with Theory
Validate against analytic dipole radiation formula.
Configuration Parameters
| Parameter | Value | Description |
|---|---|---|
resolution | 50 | Pixels per μm |
sxy | 4 | Cell size |
dpml | 1 | PML thickness |
fcen | 1.0 | Center frequency |
src_cmpt | Ex | Source component (dipole orientation) |
Complete Code
"""
Antenna Radiation Analysis with OptixLog Integration
Computes the radiation pattern of a dipole antenna and compares
the Meep near-to-far field result with analytic theory.
"""
import os
import math
import meep as mp
import matplotlib
matplotlib.use("agg")
import matplotlib.pyplot as plt
import numpy as np
from optixlog import Optixlog
def main():
api_key = os.getenv("OPTIX_API_KEY", "your_api_key_here")
client = Optixlog(api_key=api_key)
project = client.project(name="MeepExamples", create_if_not_exists=True)
# Simulation parameters
resolution = 50
sxy = 4
dpml = 1
fcen = 1.0
df = 0.4
src_cmpt = mp.Ex
run = project.run(
name="antenna_radiation_analysis",
config={
"simulation_type": "antenna_radiation",
"resolution": resolution,
"cell_size": sxy,
"center_frequency": fcen,
"source_component": "Ex"
}
)
run.log(step=0, resolution=resolution, fcen=fcen, simulation_started=True)
cell = mp.Vector3(sxy + 2 * dpml, sxy + 2 * dpml)
pml_layers = [mp.PML(dpml)]
sources = [
mp.Source(
src=mp.GaussianSource(fcen, fwidth=df),
center=mp.Vector3(),
component=src_cmpt
)
]
# Set symmetries based on source component
if src_cmpt == mp.Ex:
symmetries = [mp.Mirror(mp.X, phase=-1), mp.Mirror(mp.Y, phase=+1)]
elif src_cmpt == mp.Ey:
symmetries = [mp.Mirror(mp.X, phase=+1), mp.Mirror(mp.Y, phase=-1)]
else:
symmetries = [mp.Mirror(mp.X, phase=+1), mp.Mirror(mp.Y, phase=+1)]
sim = mp.Simulation(
cell_size=cell,
resolution=resolution,
sources=sources,
symmetries=symmetries,
boundary_layers=pml_layers,
)
# Near-to-far field transformation box
nearfield_box = sim.add_near2far(
fcen, 0, 1,
mp.Near2FarRegion(center=mp.Vector3(0, +0.5 * sxy), size=mp.Vector3(sxy, 0)),
mp.Near2FarRegion(center=mp.Vector3(0, -0.5 * sxy), size=mp.Vector3(sxy, 0), weight=-1),
mp.Near2FarRegion(center=mp.Vector3(+0.5 * sxy, 0), size=mp.Vector3(0, sxy)),
mp.Near2FarRegion(center=mp.Vector3(-0.5 * sxy, 0), size=mp.Vector3(0, sxy), weight=-1),
)
# Flux box for total power
flux_box = sim.add_flux(
fcen, 0, 1,
mp.FluxRegion(center=mp.Vector3(0, +0.5 * sxy), size=mp.Vector3(sxy, 0)),
mp.FluxRegion(center=mp.Vector3(0, -0.5 * sxy), size=mp.Vector3(sxy, 0), weight=-1),
mp.FluxRegion(center=mp.Vector3(+0.5 * sxy, 0), size=mp.Vector3(0, sxy)),
mp.FluxRegion(center=mp.Vector3(-0.5 * sxy, 0), size=mp.Vector3(0, sxy), weight=-1),
)
print("⚡ Running simulation...")
sim.run(until_after_sources=mp.stop_when_dft_decayed())
near_flux = mp.get_fluxes(flux_box)[0]
# Compute radiation pattern
r = 1000 / fcen # far-field radius
npts = 100
angles = 2 * math.pi / npts * np.arange(npts)
E = np.zeros((npts, 3), dtype=np.complex128)
H = np.zeros((npts, 3), dtype=np.complex128)
for n in range(npts):
ff = sim.get_farfield(
nearfield_box,
mp.Vector3(r * math.cos(angles[n]), r * math.sin(angles[n]))
)
E[n, :] = [ff[j] for j in range(3)]
H[n, :] = [ff[j + 3] for j in range(3)]
# Poynting vector (radial flux)
Px = np.real(np.conj(E[:, 1]) * H[:, 2] - np.conj(E[:, 2]) * H[:, 1])
Py = np.real(np.conj(E[:, 2]) * H[:, 0] - np.conj(E[:, 0]) * H[:, 2])
Pr = np.sqrt(np.square(Px) + np.square(Py))
# Analytic theory
if src_cmpt == mp.Ex:
flux_theory = np.sin(angles) ** 2
elif src_cmpt == mp.Ey:
flux_theory = np.cos(angles) ** 2
else:
flux_theory = np.ones((npts,))
# Create plots
fig = plt.figure(figsize=(15, 10))
# Polar radiation pattern
ax1 = fig.add_subplot(2, 2, 1, projection='polar')
ax1.plot(angles, Pr / max(Pr), "b-", linewidth=2, label="Meep")
ax1.plot(angles, flux_theory, "r--", linewidth=2, label="Theory")
ax1.set_rmax(1)
ax1.legend(loc='upper right')
ax1.set_title("Radiation Pattern (Polar)")
# Linear plot
ax2 = fig.add_subplot(2, 2, 2)
ax2.plot(np.degrees(angles), Pr / max(Pr), "b-", linewidth=2, label="Meep")
ax2.plot(np.degrees(angles), flux_theory, "r--", linewidth=2, label="Theory")
ax2.set_xlabel('Angle (degrees)')
ax2.set_ylabel('Normalized Power')
ax2.set_title('Radiation Pattern (Linear)')
ax2.legend()
ax2.grid(True, alpha=0.3)
# Error analysis
ax3 = fig.add_subplot(2, 2, 3)
error = np.abs(Pr / max(Pr) - flux_theory)
ax3.plot(np.degrees(angles), error, "g-", linewidth=2)
ax3.set_xlabel('Angle (degrees)')
ax3.set_ylabel('Absolute Error')
ax3.set_title('Pattern Error')
ax3.grid(True, alpha=0.3)
# Field components
ax4 = fig.add_subplot(2, 2, 4)
ax4.plot(np.degrees(angles), np.abs(E[:, 0]), 'r-', label='|Ex|')
ax4.plot(np.degrees(angles), np.abs(E[:, 1]), 'g-', label='|Ey|')
ax4.plot(np.degrees(angles), np.abs(E[:, 2]), 'b-', label='|Ez|')
ax4.set_xlabel('Angle (degrees)')
ax4.set_ylabel('Field Amplitude')
ax4.set_title('Far-Field Components')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
run.log_matplotlib("radiation_analysis", fig)
plt.close(fig)
# Log results
correlation = np.corrcoef(Pr / max(Pr), flux_theory)[0, 1]
max_radiation_angle = np.degrees(angles[np.argmax(Pr)])
run.log(step=2,
near_flux=near_flux,
correlation_coefficient=correlation,
max_radiation_angle=max_radiation_angle,
simulation_completed=True)
print(f"📊 Correlation: {correlation:.6f}")
print(f"\n✅ Simulation complete!")
if __name__ == "__main__":
main()Key Concepts
Near-to-Far Field Transformation
Define a closed surface around the source:
nearfield_box = sim.add_near2far(
fcen, 0, 1,
mp.Near2FarRegion(center=mp.Vector3(0, +0.5*sxy), size=mp.Vector3(sxy, 0)),
mp.Near2FarRegion(center=mp.Vector3(0, -0.5*sxy), size=mp.Vector3(sxy, 0), weight=-1),
# ... all four sides for 2D
)Computing Far-Field at a Point
ff = sim.get_farfield(nearfield_box, mp.Vector3(x, y))
# ff contains [Ex, Ey, Ez, Hx, Hy, Hz]
E = [ff[0], ff[1], ff[2]]
H = [ff[3], ff[4], ff[5]]Poynting Vector
Calculate radial power flow:
Px = np.real(np.conj(E[:, 1]) * H[:, 2] - np.conj(E[:, 2]) * H[:, 1])
Py = np.real(np.conj(E[:, 2]) * H[:, 0] - np.conj(E[:, 0]) * H[:, 2])
Pr = np.sqrt(Px**2 + Py**2)Expected Results
Radiation Pattern
For an x-directed dipole:
- Maximum radiation perpendicular to dipole (θ = 90°, 270°)
- Zero radiation along dipole axis (θ = 0°, 180°)
- sin²θ pattern matches theory
Correlation
The correlation coefficient between simulation and theory should be > 0.999.
OptixLog Integration
Logged Metrics
| Metric | Description |
|---|---|
near_flux | Total radiated power |
correlation_coefficient | Agreement with theory |
max_radiation_angle | Angle of maximum emission |
Logged Plots
- radiation_analysis — 4-panel plot with polar pattern, linear pattern, error, and field components