Introduction to Beamforming: Part 2

How to derive and implement the Bartlett Beamformer

Isaac Berrios
9 min readAug 5, 2024

In this post we will expand on the conventional Beamformer and introduce the Bartlett Beamformer which uses measured statistics of the current signal environment to provide a robust estimation of the Direction of Arrival. First we will derive it and then implement it, the code is included in the post, but here’s the GitHub link for reference. This post is part of a series that introduces Beamformers:

If you’re unfamiliar with with Sensor Arrays or Steering Vectors, please see this post.

Photo by Karsten Winegeart on Unsplash

Background

In this post we deal entirely with the Uniform Linear Array (ULA) with half-wavelength element spacing, but this can be expanded to cover 2D planar arrays or even 3D array structures. The conventional (Delay-and-Sum) Beamformer forms beams and scans them over a region of angular space and the computes an array response for each angle. The angle that produces the highest array response is deemed the Angle of Arrival for a given signal. This is an adequate method to estimate the Angle of Arrival using sensor arrays, in this post we will formulate a more direct way to estimate Angle of Arrival known as the Bartlett Beamformer.

Signal Snapshot Model

We will model the signal in the time-domain, with something called the snapshot model which considers the signal in snapshots of time. For all signals and models, we will use the Uniform Linear Array (ULA) with N elements and D signals (where D is unknown), the snapshot model notion is below:

We capture a total of T snapshots where each snapshot is really just a discrete time sample.

Signal Assumptions

Both desired signals and interferers are modeled such that vector f contains sinusoids at a single frequency tone, this means that the zero-mean assumption will be valid. We will also assume that there is only one signal of interest and the remaining D — 1 signals are interferers. Despite of this, we can still use the derived beamformer to detect multiple signals.

We assume that f is a Wide Sense Stationary process which tells us that it’s mean and autocorrelation do not vary with respect to time. It must also be ergodic which tells us that our signal samples provide all relevant statistical properties (i.e. we can accurately determine the mean and variance from our sample). The main benefit of the ergodic assumption is that the variance of the signal f is equal to its average power level, this is an important property that we will exploit later on.

Vectorized Model

For simplicity we will vectorize the model in the following format so that we can easily implement it.

Bartlett Beamformer

The Bartlett Beamformer is a Beamscan Algorithm which means that we scan a beam with the steering vector and collect the Array Responses at each angle. The angle with the highest response level is the estimated Angle of Arrival, if we have multiple signals than we will have multiple peaks that represent their respective Angles of Arrival. The Bartlett Beamformer is concerned with finding the Angle or Direction of Arrival for each incoming plane wave signal. Once we have the estimated directions of arrival, we can construct the steering vectors and Estimate the original signals just as we did with the Delay-and-Sum Beamformer.

Bartlett Derivation

Consider a ULA of N elements and a signal f with T snapshots. The General Beamformer is an (Nx1) complex vector w that processes the (NxT) received signal matrix X with a matrix multiplication resulting in the (1xT) Array Output y, which is the Array Response to the incoming signal X processed with Beamformer w.

If the Beamformer is ideal then we will obtain a perfect reconstruction of the original signal. In other words, if we process the received signal with its true Steering Vector (v(θₛ)), then we will obtain an exact reconstruction of the original signal f, this is the ideal Beamformer.

In reality we need to estimate the direction θₛ, we can estimate this from the Angular Power Spectrum, let’s dive in below.

For an example of the Estimated Angular Power Spectrum, see figure 2 below.

Estimating the Power

The Delay-and-Sum Beamformer uses the variance of the Array Output to estimate the Power for each angle θ, by exploiting the variance power relationship of a signal (ergodic assumption mentioned above). The Bartlett Beamformer is able to directly estimate the Power of a Signal in direction θ and it also exploits the Variance-Power relationship of a signal.

Deriving an Expression for Power

Based on our model, the power of the received signal is equal to it’s variance, and this assumption generally holds true for real situations. Let’s start with this assumption and see where it takes us.

The last line just switches to the non-vectorized signal x(t) to show that we are operating over the time samples. Another way to think about this is the correlation of the Steering Vector with each N dimensional time sample, for the true Steering Vector this should result in a large value for each time signal leading to a large power level.

This result is called the Time Average Power of the Array Output, from here, we can find a convenient expression for the power. For convenience we will drop the notion of exact steering vector and go with any angle θ. (This means that we will need to try many different steering vectors and see which one produces the most power).

Deriving the Bartlett Beamformer

Now we will derive the Bartlett Beamformer from our Time Average Power expression.

The final term computes power using only the Steering Vectors and the (NxN) sample Correlation Matrix Rₓₓ. Let’s see some examples to understand how this works.

Python Example

Let’s get started with some helper functions, the full notebook is on GitHub. Here are some functions that will help us along the way.

# generates a steering vector with default spacing of 0.5 wavelength
steering_vector = lambda theta, N, d=0.5 : np.exp(-2j * np.pi * d * np.arange(N) * np.sin(theta))

# convert between dB and Watts
db2watt = lambda db : 10**(db/10)
watt2db = lambda watt : 10*np.log10(watt)

# ref: https://github.com/morriswmz/doatools.py/blob/master/doatools/utils/math.py
def randcn(shape):
"""Samples from complex circularly-symmetric normal distribution.
Args:
shape (tuple): Shape of the output.

Returns:
~numpy.ndarray: A complex :class:`~numpy.ndarray` containing the
samples.
"""
x = 1j * np.random.randn(*shape)
x += np.random.randn(*shape)
x *= np.sqrt(0.5)
return x

The final function computes Circularly Symmetric Complex Gaussian Noise, for simplicity you can just think of it as zero-mean complex Gaussian Noise.

Implementing the Signal Model

Here are the model parameters that we will input. We also set the number of time sample, array elements, and source signals. We then input Angles of Arrival and Power level for the source signals. We determine the noise power level by subtracting the SNR from the mean of all source signals.

# input parameters
SNR = 30.0 # assume all sources have the same snr (max: 100, min: 0)
T = 1000 # number of samples
M = 10 # number of array elements
D = 5 # number of source signals
THETA_DEG = [-50, -25, 0, 25, 50] # Angle of Arrival
SOURCE_POWER = np.array([20, 20, 20, 20, 20]) # power in dB

# derived parameters
SOURCE_POWER_WATT = db2watt(SOURCE_POWER)
NOISE_POWER_DB = SOURCE_POWER.mean() - SNR
NOISE_POWER_WATT = db2watt(NOISE_POWER_DB)
SOURCE_THETAS = [theta*np.pi/180 for theta in THETA_DEG] # convert to radians

Here is how we compute the signal model the signal model, we create a covariance matrix C with our input power levels in Watts to generate the source signals with the proper amplitudes. Since the noise power is the same across all M elements, we can use a scalar multiplication to get the proper noise levels to match our input SNR value.

# construct array response
V = np.vstack([steering_vector(theta, M) for theta in SOURCE_THETAS]).T

# get random source signals
C = np.eye(D)*SOURCE_POWER_WATT # source covariance
F = C @ randcn((D, T))
# Complex Circualar AWGN
N = NOISE_POWER_WATT * randcn((M, T))

# construct array response
X = V @ F + N

We have constructed five uniformly spaced source signals with high SNRs so that we can easily estimate their Angles of Arrival. The signals are plotted below, the left shows a single source signal and the right shows the array response X from all source signals and noise.

Figure 1. Left: Single Source Signal. Right: Array Response from all five source signals. Source: Author.

Implementing Bartlett Beamforming

Now let’s get to the moment we’ve been waiting for, implementing the Bartlett beamformer. The first step is to compute the NxN Autocorrelation matrix Rₓₓ of the NxT received signal array X.

Rxx = (X @ np.conj(X).T) / X.shape[1]

Next we will iterate over an vector of possible angles and compute the power response of the Beamformer, we can increase the angular resolution of the Beamformer by increasing the resolution of this angle vector.

# collection angles to process
thetas = np.arange(-90, 90 + 0.1, 0.1)

outputs = {"bartlett" : []}
responses = {"bartlett" : []}
for _theta in thetas:
_theta *= np.pi/180
v = steering_vector(_theta, M) # Steering Vector

# Barlett Beamformer
pb = np.conj(v)[:, None].T @ Rxx @ v
wb = v / M # use normalized steering vector

# optional: estimate the actual signal
yb = (wb.conj().T @ X)

# append to lists
outputs["bartlett"].append(yb.squeeze())

# convert to power (10log10(x^0.5) = 5log10(x))
responses["bartlett"].append(5*np.log10(np.abs(pb)))

# normalize
responses["bartlett"] -= np.max(responses["bartlett"])

# obtain angle that gave us the max value
angle_idx_b = np.argmax(responses["bartlett"])
aoa_b = thetas[angle_idx_b]
s_hat_b = outputs["bartlett"][angle_idx_b]

In this loop, we compute the steering vector for the current angle and use it to compute the Power output of the Bartlett Beamformer (pb). We then use the current angle to compute the signal arriving from the current direction (yb). Now let’s inspect the Directions of Arrival, which is just a log normalized plot of the computed power at each angle. Let’s see what the results look like.

Figure 2. Spatial Spectrum Outputs from the Bartlett Beamformer. Source: Author.

We can see that all source signal Angles of Arrival are apparent by the peaks in the Bartlett Spectrum. An automated method of detection would include some sort of peak finding algorithm to determine the arrival angles.

Recovered Signals

As you saw in the loop above, we can also recover the signals, let’s see what that looks like with a single signal.

Figure 3. Original VS recovered signals. Source: Author.

The Bartlett Beamformer acts like a spatial filter that enables us to recover the source signals, even though they are heavily distorted in the array response X.

Parting Thoughts

In this post we derived the Bartlett Beamformer which exploits the Power-Variance assumption of our signal model. It has decent performance for DOA estimation and is able to accurately recover the original signal. For a slightly increased computational cost, we can get much better angular performance with another estimator called the Capon Beamformer and we will investigate this in part 3 of this series.

References

--

--