Skip to content

Waveform

Radar waveform definitions and factory functions.

Provides the :class:WaveformSample dataclass and factory functions for four waveform types: uncoded (rectangular), Barker-coded, random phase-coded, and linear frequency modulated (LFM) chirp.

BARKER_DICT = {2: [1, -1], 3: [1, 1, -1], 4: [1, 1, -1, 1], 5: [1, 1, 1, -1, 1], 7: [1, 1, 1, -1, -1, 1, -1], 11: [1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1], 13: [1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1]} module-attribute

WaveformSample dataclass

Container for a radar waveform and its key parameters.

Created by the factory functions (:func:uncoded_waveform, :func:barker_coded_waveform, :func:random_coded_waveform, :func:lfm_waveform). Call :meth:set_sample before passing to :func:rad_lab.rdm.gen to generate the discrete pulse array at the radar's sample rate.

Attributes:

Name Type Description
type WaveformType

Waveform type identifier.

bw float

Waveform bandwidth [Hz].

time_bw_product float

Time-bandwidth product [dimensionless]. Equal to 1 for uncoded pulses; greater than 1 for coded waveforms, indicating pulse-compression gain.

pulse_width float

Pulse duration [s].

pulse_func Callable

Callable that generates (time_array, pulse_array) when called with a sample rate [Hz].

pulse_sample ndarray

Discrete complex pulse array at the radar sample rate. Populated by :meth:set_sample; not set at construction time.

Source code in src/rad_lab/waveform.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@dataclass
class WaveformSample:
    """Container for a radar waveform and its key parameters.

    Created by the factory functions (:func:`uncoded_waveform`,
    :func:`barker_coded_waveform`, :func:`random_coded_waveform`,
    :func:`lfm_waveform`). Call :meth:`set_sample` before passing to
    :func:`rad_lab.rdm.gen` to generate the discrete pulse array at the
    radar's sample rate.

    Attributes:
        type: Waveform type identifier.
        bw: Waveform bandwidth [Hz].
        time_bw_product: Time-bandwidth product [dimensionless]. Equal to 1
            for uncoded pulses; greater than 1 for coded waveforms, indicating
            pulse-compression gain.
        pulse_width: Pulse duration [s].
        pulse_func: Callable that generates ``(time_array, pulse_array)`` when
            called with a sample rate [Hz].
        pulse_sample: Discrete complex pulse array at the radar sample rate.
            Populated by :meth:`set_sample`; not set at construction time.
    """

    type: WaveformType
    bw: float
    time_bw_product: float
    pulse_width: float
    pulse_func: Callable
    pulse_sample: np.ndarray = field(init=False)

    def set_sample(self, sample_rate: float) -> None:
        """Generate and store the discrete pulse array at the given sample rate.

        Args:
            sample_rate: ADC sampling rate [Hz].
        """
        _, self.pulse_sample = self.pulse_func(sample_rate)

bw instance-attribute

pulse_func instance-attribute

pulse_sample = field(init=False) class-attribute instance-attribute

pulse_width instance-attribute

time_bw_product instance-attribute

type instance-attribute

__init__(type, bw, time_bw_product, pulse_width, pulse_func)

set_sample(sample_rate)

Generate and store the discrete pulse array at the given sample rate.

Parameters:

Name Type Description Default
sample_rate float

ADC sampling rate [Hz].

required
Source code in src/rad_lab/waveform.py
57
58
59
60
61
62
63
def set_sample(self, sample_rate: float) -> None:
    """Generate and store the discrete pulse array at the given sample rate.

    Args:
        sample_rate: ADC sampling rate [Hz].
    """
    _, self.pulse_sample = self.pulse_func(sample_rate)

WaveformType

Bases: StrEnum

Enumeration of supported radar waveform types.

Source code in src/rad_lab/waveform.py
18
19
20
21
22
23
24
class WaveformType(StrEnum):
    """Enumeration of supported radar waveform types."""

    UNCODED = "uncoded"
    BARKER = "barker"
    RANDOM = "random"
    LFM = "lfm"

BARKER = 'barker' class-attribute instance-attribute

LFM = 'lfm' class-attribute instance-attribute

RANDOM = 'random' class-attribute instance-attribute

UNCODED = 'uncoded' class-attribute instance-attribute

barker_coded_pulse(sample_rate, bw, nchips, normalize=True)

Generates a baseband, Barker-coded pulse.

Barker codes are specific binary phase codes known for their low autocorrelation sidelobes.

Parameters:

Name Type Description Default
sample_rate float

The sampling rate in Hz.

required
bw float

The chip bandwidth in Hz. The chip duration is 1/bw.

required
nchips int

The number of chips in the Barker code. Must be a valid Barker code length (2, 3, 4, 5, 7, 11, or 13).

required
normalize bool

If True, the pulse is normalized to have unit energy. Defaults to True.

True

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - t (np.ndarray): Time vector for the pulse in seconds. - mag (np.ndarray): The real-valued, Barker-coded magnitude of the pulse.

Raises:

Type Description
AssertionError

If nChips is not a valid Barker code length.

Source code in src/rad_lab/waveform.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
def barker_coded_pulse(
    sample_rate: float, bw: float, nchips: int, normalize: bool = True
) -> tuple[np.ndarray, np.ndarray]:
    """Generates a baseband, Barker-coded pulse.

    Barker codes are specific binary phase codes known for their low
    autocorrelation sidelobes.

    Args:
        sample_rate (float): The sampling rate in Hz.
        bw (float): The chip bandwidth in Hz. The chip duration is 1/bw.
        nchips (int): The number of chips in the Barker code. Must be a valid
            Barker code length (2, 3, 4, 5, 7, 11, or 13).
        normalize (bool, optional): If True, the pulse is normalized to have
            unit energy. Defaults to True.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - t (np.ndarray): Time vector for the pulse in seconds.
            - mag (np.ndarray): The real-valued, Barker-coded magnitude of the pulse.

    Raises:
        AssertionError: If nChips is not a valid Barker code length.
    """
    assert nchips in BARKER_DICT, f"{nchips=} is not a valid Barker code length."
    return coded_pulse(
        sample_rate,
        bw,
        BARKER_DICT[nchips],
        normalize=normalize,
    )

barker_coded_waveform(bw, nchips)

Returns a WaveformSample for a Barker-coded pulse.

Parameters:

Name Type Description Default
bw float

Chip bandwidth in Hz. Chip duration is 1/bw.

required
nchips int

Number of chips. Must be a valid Barker length (2,3,4,5,7,11,13).

required

Returns:

Type Description
WaveformSample

WaveformSample for use with rdm.gen.

Source code in src/rad_lab/waveform.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
def barker_coded_waveform(bw: float, nchips: int) -> WaveformSample:
    """Returns a WaveformSample for a Barker-coded pulse.

    Args:
        bw: Chip bandwidth in Hz. Chip duration is 1/bw.
        nchips: Number of chips. Must be a valid Barker length (2,3,4,5,7,11,13).

    Returns:
        WaveformSample for use with rdm.gen.
    """
    assert nchips in BARKER_DICT, f"nchips={nchips} is not a valid Barker code length."
    return WaveformSample(
        type=WaveformType.BARKER,
        bw=bw,
        time_bw_product=nchips,
        pulse_width=nchips / bw,
        pulse_func=partial(barker_coded_pulse, bw=bw, nchips=nchips),
    )

coded_pulse(sample_rate, bw, code, normalize=True)

Generates a baseband, phase-coded pulse.

The pulse is constructed by concatenating rectangular "chips", where each chip has a phase determined by the input code (1 for 0 phase, -1 for pi phase).

Parameters:

Name Type Description Default
sample_rate float

The sampling rate in Hz.

required
bw float

The chip bandwidth in Hz. The chip duration is 1/bw.

required
code list[int]

A list of code values, which must be either 1 or -1. The length of the list determines the number of chips.

required
normalize bool

If True, the pulse is normalized to have unit energy. Defaults to True.

True

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - t (np.ndarray): Time vector for the pulse in seconds. - mag (np.ndarray): The real-valued, coded magnitude of the pulse.

Raises:

Type Description
AssertionError

If any value in the code is not 1 or -1.

Source code in src/rad_lab/waveform.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def coded_pulse(
    sample_rate: float, bw: float, code: list[int], normalize: bool = True
) -> tuple[np.ndarray, np.ndarray]:
    """Generates a baseband, phase-coded pulse.

    The pulse is constructed by concatenating rectangular "chips", where each
    chip has a phase determined by the input code (1 for 0 phase, -1 for pi
    phase).

    Args:
        sample_rate (float): The sampling rate in Hz.
        bw (float): The chip bandwidth in Hz. The chip duration is 1/bw.
        code (list[int]): A list of code values, which must be either 1 or -1.
            The length of the list determines the number of chips.
        normalize (bool, optional): If True, the pulse is normalized to have
            unit energy. Defaults to True.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - t (np.ndarray): Time vector for the pulse in seconds.
            - mag (np.ndarray): The real-valued, coded magnitude of the pulse.

    Raises:
        AssertionError: If any value in the code is not 1 or -1.
    """
    Tc = 1 / bw
    dt = 1 / sample_rate
    samples_per_chip = round(Tc * sample_rate)

    code_array = np.asarray(code)
    assert np.all(np.abs(code_array) == 1), "Code values must be either 1 or -1."
    mag = np.repeat(code_array, samples_per_chip).astype(float)
    t = np.arange(mag.size) * dt

    if normalize:
        mag = mag / norm(mag)

    return t, mag

complex_tone_pulse(sample_rate, bw, fc, normalize=True)

Generates a complex-valued pulse with a constant frequency offset.

This function creates a rectangular pulse and modulates it with a complex exponential at a given carrier frequency.

Parameters:

Name Type Description Default
sample_rate float

The sampling rate in Hz.

required
bw float

The pulse bandwidth in Hz. The pulse duration is 1/bw.

required
fc float

The carrier frequency offset in Hz.

required
normalize bool

If True, the pulse is normalized to have unit energy. Defaults to True.

True

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - t (np.ndarray): Time vector for the pulse in seconds. - mag_c (np.ndarray): The complex-valued samples of the pulse.

Raises:

Type Description
AssertionError

If the sample rate is below the Nyquist rate (2 * bw).

Source code in src/rad_lab/waveform.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def complex_tone_pulse(
    sample_rate: float, bw: float, fc: float, normalize: bool = True
) -> tuple[np.ndarray, np.ndarray]:
    """Generates a complex-valued pulse with a constant frequency offset.

    This function creates a rectangular pulse and modulates it with a complex
    exponential at a given carrier frequency.

    Args:
        sample_rate (float): The sampling rate in Hz.
        bw (float): The pulse bandwidth in Hz. The pulse duration is 1/bw.
        fc (float): The carrier frequency offset in Hz.
        normalize (bool, optional): If True, the pulse is normalized to have
            unit energy. Defaults to True.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - t (np.ndarray): Time vector for the pulse in seconds.
            - mag_c (np.ndarray): The complex-valued samples of the pulse.

    Raises:
        AssertionError: If the sample rate is below the Nyquist rate (2 * bw).
    """
    t, mag = uncoded_pulse(sample_rate, bw, normalize=normalize)
    mag_c = np.exp(2j * c.PI * fc * t) * mag
    return t, mag_c

lfm_pulse(sample_rate, bw, T, chirp_up_down, normalize=True)

Generates a baseband Linear Frequency Modulated (LFM) pulse (chirp).

The instantaneous frequency of the pulse varies linearly with time over the pulse duration.

Parameters:

Name Type Description Default
sample_rate float

The sampling rate in Hz.

required
bw float

The bandwidth of the frequency sweep in Hz.

required
T float

The total pulse duration in seconds.

required
chirp_up_down int

Determines the direction of the frequency sweep. 1 for an up-chirp (frequency increases), -1 for a down-chirp (frequency decreases).

required
normalize bool

If True, the pulse is normalized to have unit energy. Defaults to True.

True

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - t (np.ndarray): Time vector for the pulse in seconds. - mag (np.ndarray): The complex-valued samples of the LFM pulse.

Raises:

Type Description
AssertionError

If chirp_up_down is not 1 or -1.

Source code in src/rad_lab/waveform.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
def lfm_pulse(
    sample_rate: float, bw: float, T: float, chirp_up_down: int, normalize: bool = True
) -> tuple[np.ndarray, np.ndarray]:
    """Generates a baseband Linear Frequency Modulated (LFM) pulse (chirp).

    The instantaneous frequency of the pulse varies linearly with time over the
    pulse duration.

    Args:
        sample_rate (float): The sampling rate in Hz.
        bw (float): The bandwidth of the frequency sweep in Hz.
        T (float): The total pulse duration in seconds.
        chirp_up_down (int): Determines the direction of the frequency sweep.
            1 for an up-chirp (frequency increases), -1 for a down-chirp
            (frequency decreases).
        normalize (bool, optional): If True, the pulse is normalized to have
            unit energy. Defaults to True.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - t (np.ndarray): Time vector for the pulse in seconds.
            - mag (np.ndarray): The complex-valued samples of the LFM pulse.

    Raises:
        AssertionError: If chirp_up_down is not 1 or -1.
    """
    assert chirp_up_down in [1, -1], "chirp_up_down must be either 1 or -1."
    dt = 1 / sample_rate
    t = np.arange(0, T, dt)
    k = bw / T  # Chirp rate
    phase = chirp_up_down * np.pi * k * (t**2) - chirp_up_down * np.pi * bw * t
    mag = np.exp(1j * phase)

    if normalize:
        mag = mag / norm(mag)

    return t, mag

lfm_waveform(bw, T, chirp_up_down)

Returns a WaveformSample for a Linear Frequency Modulated (LFM) pulse.

Parameters:

Name Type Description Default
bw float

Bandwidth of the frequency sweep in Hz.

required
T float

Pulse duration in seconds.

required
chirp_up_down int

1 for up-chirp, -1 for down-chirp.

required

Returns:

Type Description
WaveformSample

WaveformSample for use with rdm.gen.

Source code in src/rad_lab/waveform.py
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def lfm_waveform(bw: float, T: float, chirp_up_down: int) -> WaveformSample:
    """Returns a WaveformSample for a Linear Frequency Modulated (LFM) pulse.

    Args:
        bw: Bandwidth of the frequency sweep in Hz.
        T: Pulse duration in seconds.
        chirp_up_down: 1 for up-chirp, -1 for down-chirp.

    Returns:
        WaveformSample for use with rdm.gen.
    """
    assert chirp_up_down in [1, -1], "chirp_up_down must be 1 (up) or -1 (down)."
    return WaveformSample(
        type=WaveformType.LFM,
        bw=bw,
        time_bw_product=bw * T,
        pulse_width=T,
        pulse_func=partial(lfm_pulse, bw=bw, T=T, chirp_up_down=chirp_up_down),
    )

random_coded_pulse(sample_rate, bw, nchips, normalize=True)

Generates a baseband pulse with a random binary phase code.

The code consists of a sequence of randomly chosen 1s and -1s.

Parameters:

Name Type Description Default
sample_rate float

The sampling rate in Hz.

required
bw float

The chip bandwidth in Hz. The chip duration is 1/bw.

required
nchips int

The number of chips in the random code.

required
normalize bool

If True, the pulse is normalized to have unit energy. Defaults to True.

True

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - t (np.ndarray): Time vector for the pulse in seconds. - mag (np.ndarray): The real-valued, randomly coded magnitude.

Source code in src/rad_lab/waveform.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def random_coded_pulse(
    sample_rate: float, bw: float, nchips: int, normalize: bool = True
) -> tuple[np.ndarray, np.ndarray]:
    """Generates a baseband pulse with a random binary phase code.

    The code consists of a sequence of randomly chosen 1s and -1s.

    Args:
        sample_rate (float): The sampling rate in Hz.
        bw (float): The chip bandwidth in Hz. The chip duration is 1/bw.
        nchips (int): The number of chips in the random code.
        normalize (bool, optional): If True, the pulse is normalized to have
            unit energy. Defaults to True.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - t (np.ndarray): Time vector for the pulse in seconds.
            - mag (np.ndarray): The real-valued, randomly coded magnitude.
    """
    code_rand = np.random.choice([1, -1], size=nchips)
    return coded_pulse(
        sample_rate,
        bw,
        code_rand,
        normalize=normalize,
    )

random_coded_waveform(bw, nchips)

Returns a WaveformSample for a random binary phase-coded pulse.

Parameters:

Name Type Description Default
bw float

Chip bandwidth in Hz. Chip duration is 1/bw.

required
nchips int

Number of chips.

required

Returns:

Type Description
WaveformSample

WaveformSample for use with rdm.gen.

Source code in src/rad_lab/waveform.py
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
def random_coded_waveform(bw: float, nchips: int) -> WaveformSample:
    """Returns a WaveformSample for a random binary phase-coded pulse.

    Args:
        bw: Chip bandwidth in Hz. Chip duration is 1/bw.
        nchips: Number of chips.

    Returns:
        WaveformSample for use with rdm.gen.
    """
    return WaveformSample(
        type=WaveformType.RANDOM,
        bw=bw,
        time_bw_product=nchips,
        pulse_width=nchips / bw,
        pulse_func=partial(random_coded_pulse, bw=bw, nchips=nchips),
    )

uncoded_pulse(sample_rate, bw, normalize=True)

Generates a simple, baseband rectangular (uncoded) pulse.

Parameters:

Name Type Description Default
sample_rate float

The sampling rate in Hz.

required
bw float

The pulse bandwidth in Hz. The pulse duration is 1/bw.

required
normalize bool

If True, the pulse is normalized to have unit energy. Defaults to True.

True

Returns:

Type Description
tuple[ndarray, ndarray]

tuple[np.ndarray, np.ndarray]: A tuple containing: - t (np.ndarray): Time vector for the pulse in seconds. - mag (np.ndarray): The real-valued magnitude of the pulse samples.

Raises:

Type Description
AssertionError

If the sample rate is below the Nyquist rate (2 * bw).

Source code in src/rad_lab/waveform.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def uncoded_pulse(
    sample_rate: float, bw: float, normalize: bool = True
) -> tuple[np.ndarray, np.ndarray]:
    """Generates a simple, baseband rectangular (uncoded) pulse.

    Args:
        sample_rate (float): The sampling rate in Hz.
        bw (float): The pulse bandwidth in Hz. The pulse duration is 1/bw.
        normalize (bool, optional): If True, the pulse is normalized to have
            unit energy. Defaults to True.

    Returns:
        tuple[np.ndarray, np.ndarray]: A tuple containing:
            - t (np.ndarray): Time vector for the pulse in seconds.
            - mag (np.ndarray): The real-valued magnitude of the pulse samples.

    Raises:
        AssertionError: If the sample rate is below the Nyquist rate (2 * bw).
    """
    assert sample_rate / bw >= 2, "sample rate must be >= 2 * bw (Nyquist)"

    T = 1 / bw
    dt = 1 / sample_rate
    t = np.arange(0, T, dt)
    mag = np.ones(t.size)

    if normalize:
        mag = mag / norm(mag)

    return t, mag

uncoded_waveform(bw)

Returns a WaveformSample for an uncoded rectangular pulse.

Parameters:

Name Type Description Default
bw float

Pulse bandwidth in Hz. Pulse duration is 1/bw.

required

Returns:

Type Description
WaveformSample

WaveformSample for use with rdm.gen.

Source code in src/rad_lab/waveform.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
def uncoded_waveform(bw: float) -> WaveformSample:
    """Returns a WaveformSample for an uncoded rectangular pulse.

    Args:
        bw: Pulse bandwidth in Hz. Pulse duration is 1/bw.

    Returns:
        WaveformSample for use with rdm.gen.
    """
    return WaveformSample(
        type=WaveformType.UNCODED,
        bw=bw,
        time_bw_product=1,
        pulse_width=1 / bw,
        pulse_func=partial(uncoded_pulse, bw=bw),
    )