ExamplesMeep Examples

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:

  1. Record tangential E and H fields on a closed surface
  2. Use surface currents as equivalent sources
  3. 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

ParameterValueDescription
resolution50Pixels per μm
sxy4Cell size
dpml1PML thickness
fcen1.0Center frequency
src_cmptExSource 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

MetricDescription
near_fluxTotal radiated power
correlation_coefficientAgreement with theory
max_radiation_angleAngle of maximum emission

Logged Plots

  • radiation_analysis — 4-panel plot with polar pattern, linear pattern, error, and field components

Further Reading

On this page