Grating Lobes in Electronic Scanning

Articles in this series

Introduction

A uniform array samples the aperture field at equal intervals. Sample too coarsely and the far-field pattern aliases—the same idea as undersampling a time signal. The aliases show up as grating lobes: extra peaks that look like main beams, steal power, and create false directions.

Motive

How far apart may the elements be, and how far may you scan, before a grating lobe enters visible space? We want one clear rule and a few plots that check it.

Technical development

Peaks of the array factor repeat when

\[u_m = u_0 + m\,\frac{\lambda}{d}, \quad m=0,\pm 1,\pm 2,\ldots\]

Only those \(u_m\) with \(|u|\le 1\) appear as real angles. Asking the first aliases to stay outside the visible region gives

\[|u_0|_{\max} = \frac{\lambda}{d} – 1\]

(when that quantity is positive). At \(d=\lambda/2\) you may scan the whole visible region; larger spacing shrinks the safe scan cone.

Grating vs spacing
Figure 1: Broadside patterns for \(d=\lambda/2\), \(\lambda\), and \(1.5\lambda\).
Scanned grating lobes
Figure 2: With \(d=\lambda\), steering to \(u_0=0.5\) pulls a grating lobe to \(u=-0.5\).
Scan spacing limit
Figure 3: Scan–spacing budget \(|u_0|_{\max}=\lambda/d-1\).

How this achieves the motive

Figure 1 shows aliases appearing as soon as \(d\ge\lambda\) at broadside. Figure 2 shows how scanning moves those aliases. Figure 3 summarises the design inequality. Together they answer the spacing-and-scan question.

Sample simulation

import numpy as np
import matplotlib.pyplot as plt

def array_factor(u, N, d_over_lambda, u0=0.0, weights=None):
    """Normalized |AF(u)|^2, u=sin(theta)."""
    if weights is None:
        weights = np.ones(N)
    weights = np.asarray(weights, dtype=float)
    weights = weights / np.max(np.abs(weights))
    kdl = 2 * np.pi * d_over_lambda
    af = np.zeros_like(u, dtype=complex)
    for n in range(N):
        af += weights[n] * np.exp(1j * n * kdl * (u - u0))
    g = np.abs(af) ** 2
    return g / np.max(g)

def db(g, floor=-60.0):
    return 10 * np.log10(np.maximum(g, 10 ** (floor / 10)))

u = np.linspace(-1, 1, 4001)
N = 24
plt.figure(figsize=(8, 4.8))
for d, lab in ((0.5, "d=lambda/2"), (1.0, "d=lambda"), (1.5, "d=1.5 lambda")):
    plt.plot(u, db(array_factor(u, N, d, u0=0.0)), label=lab)
plt.xlabel("u"); plt.ylabel("dB"); plt.ylim(-45, 2); plt.grid(True); plt.legend(); plt.show()
d_over = np.linspace(0.35, 1.6, 400)
u_max = np.maximum(0.0, 1.0/d_over - 1.0)
plt.plot(d_over, u_max); plt.fill_between(d_over, 0, u_max, alpha=0.12)
plt.axvline(0.5, linestyle="--"); plt.xlabel("d/lambda"); plt.ylabel("|u0|_max"); plt.grid(True); plt.show()

Sample results

The first plot should match Figure 1: one beam for \(d=\lambda/2\), edge lobes for \(d=\lambda\), interior lobes for \(d=1.5\lambda\). The budget plot should match Figure 3, touching \(|u_0|=1\) at \(d=\lambda/2\).

Next topic

Real elements are not isotropic. Next we multiply the array factor by an element pattern and see how amplitude tapering trades sidelobes for beamwidth.

References

[1] Balanis. [2] Mailloux, grating-lobe chapters.

Leave a Comment