Phased-Array Source Design#
pycsamt.emtools.source_array is the transmitter-design module in
emtools. Unlike most pages in this section, it does not load EDI
files or operate on Sites objects. It implements formulas for a
phased-array transmitting source (PAS): a line of controlled-source
audio-frequency magnetotelluric dipoles whose relative phases can steer
and narrow the transmitted beam.
The traditional source is a single-dipole antenna source, abbreviated
SDAS. The phased-array source combines N co-linear SDAS elements with
spacing d and inter-element phase shift beta. The module helps
you answer practical design questions:
What wavelength should be used at CSAMT frequencies?
How directional is one finite-length dipole?
How much does an
N-element array narrow the beam?Which phase shift steers the beam to the area of interest?
Are there unwanted grating lobes?
How much SNR gain is expected from coherent arraying?
Full function signatures and parameter defaults are maintained in the
API reference. The examples below use the
public two-level imports from pycsamt.emtools.
Concepts And Angles#
The module uses two related angle conventions:
Symbol |
Used by |
Meaning |
|---|---|---|
|
|
Angle from the dipole axis. |
|
|
Broadside angle. |
|
|
Desired main-lobe broadside angle. |
This distinction matters. The element pattern is written in the dipole
axis convention, while the array factor is written in the broadside
convention. pas_pattern handles the conversion internally.
Workflow Map#
Task |
Use this |
Output |
|---|---|---|
Compute propagation wavenumber |
|
Earth or free-space wavenumber |
Model one finite SDAS dipole |
|
Normalized amplitude pattern versus dipole-axis angle. |
Model array interference |
|
Normalized array factor versus broadside angle. |
Combine element and array effects |
|
Total PAS amplitude pattern. |
Steer the main beam |
|
Inter-element phase shift |
Check all beam solutions |
|
Target beam and any grating-lobe angles. |
Estimate single-source directivity |
|
Dimensionless directivity from numerical integration. |
Estimate coherent SNR gain |
|
Gain in decibels, |
Plot patterns |
|
Polar or Cartesian pattern figure. |
Earth Wavenumber#
For CSAMT transmitter design, use the effective earth wavenumber when a representative half-space resistivity is known:
If rho is omitted, wavenumber returns the free-space value
2*pi*f/c. That can be useful as a reference, but it is usually the
wrong scale for CSAMT array geometry.
1import numpy as np
2
3from pycsamt.emtools import wavenumber
4
5freq = 8.0 # Hz
6rho = 300.0 # ohm.m
7
8k_earth = wavenumber(freq, rho=rho)
9k_free = wavenumber(freq)
10
11wavelength_earth = 2.0 * np.pi / k_earth
12wavelength_free = 2.0 * np.pi / k_free
13
14print(f"earth wavelength: {wavelength_earth:,.0f} m")
15print(f"free-space wavelength: {wavelength_free:,.0f} m")
earth wavelength: 19,365 m
free-space wavelength: 37,474,057 m
Use the earth wavelength to judge whether the chosen element spacing is small, moderate, or large in wavelengths:
1d = 2000.0
2print(f"d / earth wavelength = {d / wavelength_earth:.3f}")
d / earth wavelength = 0.103
A physical spacing that is small at low frequency can become larger than one wavelength at a high CSAMT frequency. That is when side lobes and grating lobes become much more important.
Single-Dipole Element Pattern#
sdas_element_pattern computes the finite-length SDAS element pattern:
Here theta is measured from the dipole axis. Along the dipole axis
the response is a null. Broadside to the dipole the normalized response
is usually the maximum.
1import matplotlib.pyplot as plt
2import numpy as np
3
4from pycsamt.emtools import sdas_element_pattern, wavenumber
5
6freq = 8.0
7rho = 300.0
8length = 1000.0
9k = wavenumber(freq, rho=rho)
10
11theta = np.linspace(0.0, 180.0, 721)
12element = sdas_element_pattern(theta, l=length, k=k)
13
14print(element[0], element[360], element[-1])
15
16fig, ax = plt.subplots(figsize=(7, 4))
17ax.plot(theta, element)
18ax.set_xlabel("Angle from dipole axis (deg)")
19ax.set_ylabel("Normalized amplitude")
20ax.grid(True, alpha=0.3)
21fig.tight_layout()
22fig.savefig("sdas_element_pattern.png", dpi=200)
23plt.close(fig)
0.0 1.0 0.0
Set normalize=False when you need the unnormalized formula value,
for example before computing directivity or comparing absolute pattern
shape for different lengths.
Array Factor#
array_factor models interference among N equally spaced
co-linear source elements:
The angle theta_b is measured from broadside. With beta=0, the
main lobe is broadside at theta_b = 0.
1import numpy as np
2
3from pycsamt.emtools import array_factor, plot_radiation_pattern, wavenumber
4
5freq = 8.0
6rho = 300.0
7d = 2000.0
8k = wavenumber(freq, rho=rho)
9
10theta_b = np.linspace(-90.0, 90.0, 721)
11patterns = [
12 array_factor(theta_b, N=1, d=d, k=k),
13 array_factor(theta_b, N=2, d=d, k=k),
14 array_factor(theta_b, N=4, d=d, k=k),
15 array_factor(theta_b, N=8, d=d, k=k),
16]
17
18plot_radiation_pattern(
19 theta_b,
20 patterns,
21 labels=["N=1", "N=2", "N=4", "N=8"],
22 title="Array factor at 8 Hz",
23)
24plt.gcf().savefig("array_factor_8hz.png", dpi=200)
25plt.close(plt.gcf())
At low frequency, a kilometer-scale array may be much smaller than one earth wavelength. It can still narrow the beam, but it may not form sharp nulls. Recompute the pattern at the high end of the frequency sweep before trusting a design.
High-Frequency Check#
The same physical layout can behave very differently at higher frequency.
1import numpy as np
2
3from pycsamt.emtools import array_factor, plot_radiation_pattern, wavenumber
4
5rho = 300.0
6d = 2000.0
7freq = 1024.0
8k = wavenumber(freq, rho=rho)
9wavelength = 2.0 * np.pi / k
10
11theta_b = np.linspace(-90.0, 90.0, 721)
12patterns = [array_factor(theta_b, N=n, d=d, k=k) for n in (1, 2, 4, 8)]
13
14print(f"d / wavelength = {d / wavelength:.3f}")
15
16plot_radiation_pattern(
17 theta_b,
18 patterns,
19 labels=["N=1", "N=2", "N=4", "N=8"],
20 polar=False,
21 log_scale=True,
22 title="Array factor at 1024 Hz",
23)
24plt.gcf().savefig("array_factor_1024hz.png", dpi=200)
25plt.close(plt.gcf())
d / wavelength = 1.168
When d / wavelength approaches or exceeds 1, grating lobes are
likely. A design that looks clean at low frequency can radiate strongly
in unintended directions at high frequency.
Beam Steering#
beam_steer computes the inter-element phase shift required to steer
the main lobe:
Use steering_angles immediately after beam_steer. It solves the
periodic steering equation and reveals whether the requested beam has
additional main-lobe solutions.
1import numpy as np
2
3from pycsamt.emtools import (
4 array_factor,
5 beam_steer,
6 steering_angles,
7 wavenumber,
8)
9
10freq = 1024.0
11rho = 300.0
12d = 2000.0
13N = 4
14target_angle = 20.0
15
16k = wavenumber(freq, rho=rho)
17beta = beam_steer(target_angle, d=d, k=k)
18
19theta_b = np.linspace(-90.0, 90.0, 1801)
20af = array_factor(theta_b, N=N, d=d, k=k, beta=beta)
21peak_angle = theta_b[np.argmax(af)]
22all_lobes = steering_angles(N=N, d=d, k=k, beta=beta, n_range=3)
23
24print(f"beta = {beta:.4f} rad")
25print(f"peak angle = {peak_angle:.2f} deg")
26print(f"all steering-angle solutions = {all_lobes}")
beta = -2.5110 rad
peak angle = 20.00 deg
all steering-angle solutions = [-30.917035 20. ]
The peak angle should match the target angle on a fine angular grid. If
steering_angles returns multiple values, the design has grating-lobe
directions that deserve explicit review.
Combined PAS Pattern#
pas_pattern multiplies the single-dipole element pattern by the
array factor. This is the practical radiation pattern for the phased
array.
1import numpy as np
2
3from pycsamt.emtools import (
4 beam_steer,
5 pas_pattern,
6 plot_radiation_pattern,
7 wavenumber,
8)
9
10theta_b = np.linspace(-90.0, 90.0, 721)
11freq = 1024.0
12rho = 300.0
13d = 2000.0
14length = 1000.0
15N = 4
16
17k = wavenumber(freq, rho=rho)
18beta = beam_steer(20.0, d=d, k=k)
19
20broadside = pas_pattern(theta_b, N=N, d=d, k=k, beta=0.0, l=length)
21steered = pas_pattern(theta_b, N=N, d=d, k=k, beta=beta, l=length)
22
23plot_radiation_pattern(
24 theta_b,
25 [broadside, steered],
26 labels=["broadside", "steered to 20 deg"],
27 title="Combined PAS pattern",
28)
29plt.gcf().savefig("combined_pas_pattern.png", dpi=200)
30plt.close(plt.gcf())
Use pas_pattern for design figures. Use array_factor alone when
you want to isolate what the array geometry contributes independently of
the finite dipole element pattern.
Directivity#
sdas_directivity numerically integrates the single-dipole element
pattern and returns dimensionless directivity. A value of 1 would be
omnidirectional in the integration convention; a larger value means more
concentrated radiation.
1from pycsamt.emtools import sdas_directivity, wavenumber
2
3freq = 1024.0
4rho = 300.0
5k = wavenumber(freq, rho=rho)
6
7for length in (500.0, 1000.0, 2000.0, 5000.0):
8 directivity = sdas_directivity(length, k=k, n_theta=2000)
9 print(f"length={length:7.0f} m directivity={directivity:.3f}")
length= 500 m directivity=1.544
length= 1000 m directivity=1.703
length= 2000 m directivity=3.039
length= 5000 m directivity=2.985
Do not assume that a longer dipole is always better. Once length becomes a significant fraction of wavelength, directivity can change non-monotonically with frequency and length.
SNR Gain#
snr_gain_db returns the coherent PAS gain relative to one SDAS:
1from pycsamt.emtools import snr_gain_db
2
3for n_elem in (1, 2, 4, 8, 16):
4 print(f"N={n_elem:2d}: {snr_gain_db(n_elem):5.2f} dB")
N= 1: 0.00 dB
N= 2: 6.02 dB
N= 4: 12.04 dB
N= 8: 18.06 dB
N=16: 24.08 dB
This is the ideal coherent-array gain. It does not guarantee that all extra energy reaches the intended area. If grating lobes are present, a large fraction of the gain can be radiated into an unintended direction.
Plotting Patterns#
plot_radiation_pattern accepts one pattern or a stack/list of
patterns. It can draw polar plots for quick design review or Cartesian
dB plots for side-lobe inspection.
1import matplotlib.pyplot as plt
2import numpy as np
3
4from pycsamt.emtools import array_factor, plot_radiation_pattern, wavenumber
5
6theta_b = np.linspace(-90.0, 90.0, 721)
7k = wavenumber(1024.0, rho=300.0)
8d = 2000.0
9
10patterns = [array_factor(theta_b, N=n, d=d, k=k) for n in (2, 4, 8)]
11
12fig, axes = plt.subplots(1, 2, figsize=(12, 4))
13
14plot_radiation_pattern(
15 theta_b,
16 patterns,
17 labels=["N=2", "N=4", "N=8"],
18 polar=False,
19 ax=axes[0],
20 title="Linear amplitude",
21)
22
23plot_radiation_pattern(
24 theta_b,
25 patterns,
26 labels=["N=2", "N=4", "N=8"],
27 polar=False,
28 log_scale=True,
29 db_floor=-40.0,
30 ax=axes[1],
31 title="dB view",
32)
33fig.tight_layout()
34fig.savefig("source_array_pattern_views.png", dpi=200)
35plt.close(fig)
Use the dB view when side lobes matter. A weak-looking side lobe on a linear-amplitude plot may still be too strong for a field design.
Design Checklist#
For a concrete PAS design, keep the calculation explicit:
1import numpy as np
2
3from pycsamt.emtools import (
4 beam_steer,
5 pas_pattern,
6 plot_radiation_pattern,
7 snr_gain_db,
8 steering_angles,
9 wavenumber,
10)
11
12freq = 1024.0
13rho = 300.0
14N = 8
15d = 2000.0
16length = 1000.0
17target_angle = 25.0
18theta_b = np.linspace(-90.0, 90.0, 1801)
19
20k = wavenumber(freq, rho=rho)
21wavelength = 2.0 * np.pi / k
22beta = beam_steer(target_angle, d=d, k=k)
23lobes = steering_angles(N=N, d=d, k=k, beta=beta, n_range=4)
24pattern = pas_pattern(theta_b, N=N, d=d, k=k, beta=beta, l=length)
25
26print(f"earth wavelength = {wavelength:.1f} m")
27print(f"d / wavelength = {d / wavelength:.3f}")
28print(f"beta = {beta:.4f} rad")
29print(f"steering-angle solutions = {lobes}")
30print(f"ideal coherent SNR gain = {snr_gain_db(N):.2f} dB")
31
32plot_radiation_pattern(
33 theta_b,
34 pattern,
35 polar=False,
36 log_scale=True,
37 title="Final PAS design check",
38)
39plt.gcf().savefig("final_pas_design_check.png", dpi=200)
40plt.close(plt.gcf())
earth wavelength = 1711.6 m
d / wavelength = 1.168
beta = -3.1028 rad
steering-angle solutions = [-25.6707 25. ]
ideal coherent SNR gain = 18.06 dB
Review the output in this order:
d / wavelengthtells you whether grating lobes are physically plausible.steering_anglestells you where all main-lobe solutions are.The radiation pattern shows main-lobe width and side-lobe strength.
snr_gain_dbtells you the ideal coherent gain from element count.
Common Pitfalls#
Do not design from free-space wavelength at CSAMT frequencies. Use
wavenumber(freq, rho=...) when a representative resistivity is
available.
Do not check only one frequency. A layout with modest spacing at low
frequency can have d / wavelength > 1 at the high end of the sweep.
Do not treat SNR gain as directional selectivity. snr_gain_db(8) is
about 18 dB, but grating lobes can send much of that coherent energy
away from the intended target.
Do not mix the angle conventions. sdas_element_pattern uses angle
from the dipole axis; array_factor and pas_pattern use broadside
angle.
Worked Example#
The example walks through the single-dipole pattern, earth versus free-space wavenumber, low- and high-frequency array factors, beam steering, grating-lobe detection, combined PAS patterns, directivity, SNR gain, and one concrete 8-element design.
Open the rendered gallery page here: Phased-array CSAMT transmitter design (pycsamt.emtools.source_array).