Optimizing chirp parameters for accuracy

image0

The problem

Range range, resolution and accuracy is a function of the chirp definition.

While defining optimal chirp bandwidth, slope, sampling frequency and maximum ADC buffer size is a fairly simple problem to solve for a single target.

Optimizing chirp definition to minimise measurement error over multiple targets can be a more daunting task.

The solution

This workbook shows a simple non-optimised solution for this multi-target error minimisation.

A likely improvement would be to leverage scipy.optimize.minimize

[1]:
# Install a pip package in the current Jupyter kernel
import sys
from os.path import abspath, basename, join, pardir
import datetime

# hack to handle if running from git cloned folder or stand alone (like Google Colab)
cw = basename(abspath(join(".")))
dp = abspath(join(".",pardir))
if cw=="docs" and basename(dp) == "mmWrt":
    # running from cloned folder
    print("running from git folder, using local path (latest) mmWrt code", dp)
    sys.path.insert(0, dp)
else:
    print("running standalone, need to ensure mmWrt is installed")
    !{sys.executable} -m pip install mmWrt
print(datetime.datetime.now())
running from git folder, using local path (latest) mmWrt code c:\git\mmWrt
2026-07-17 18:47:35.574817
[2]:
from os.path import abspath, join, pardir
import sys
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib import colors
from numpy import arange, where, expand_dims, abs as np_abs

from mmWrt.Raytracing import rt_points  # noqa: E402
from mmWrt.Scene import Radar, Transmitter, Receiver, Scatterer  # noqa: E402
from mmWrt import RadarSignalProcessing as rsp  # noqa: E402
from mmWrt import __version__ as mmWrt_ver
print(mmWrt_ver)

from tqdm import tqdm
0.0.14.dev1+g243a05bf3.d20260717
[3]:
c = 3e8
debug_code = True

scatterer1 = Scatterer(5.1)

min_error = scatterer1.distance()
config = {"bw": "?", "adc_sample_rate": "?", "error": "?"}

bws = arange(30, 40)*1e8
slopes = arange(1, 10)*1e12
adc_sample_rates = arange(1, 100)*1e5  # intentionally small lower bound

with tqdm(total=len(bws) * len(slopes) * len(adc_sample_rates)) as pbar:
    for bw in bws:
        for slope in slopes:
            # slope = slope_m * 1e8
            for adc_sample_rate in adc_sample_rates:
                pbar.update(1)
                try:

                    chirp_end_time = bw/slope
                    transmitter = Transmitter(chirp_end_time=chirp_end_time,
                                              chirp_slope=slope)
                    receiver0 = Receiver(adc_sample_rate=adc_sample_rate,
                                         adc_sample_rate_max=adc_sample_rate+1,
                                         adc_sample_count=32)

                    radar = Radar(transmitter=transmitter,
                                  receiver=receiver0)

                    bb = rt_points([radar], [scatterer1],
                                   radar)
                    Distances, range_profile = rsp.range_fft(bb["adc_cube"]
                                                             [0, 0, 0, :], bb)
                    Distances = Distances/2

                    cfar_ca = rsp.cfar_ca(range_profile, train_cell_count=5,
                                          guard_cell_count=1, pfa=1e-6)

                    mag_r = np_abs(range_profile)
                    mag_c = np_abs(cfar_ca)

                    # little hack to remove small FFT ripples : mag_r> 5
                    scatterer_filter = ((mag_r > mag_c) & (mag_r > 5))

                    index_peaks = where(scatterer_filter)[0]
                    if index_peaks.size > 0:
                        grouped_peaks = rsp.peak_grouping_1d(index_peaks, mag_r)

                        found_scatterers = [Scatterer(Distances[i])
                                            for i in grouped_peaks]
                        error = rsp.error([scatterer1], found_scatterers)
                        # print("error", error)
                        if error < min_error:
                            min_error = error
                            config = {"bw": float(bw),
                                      "adc_sample_rate": float(adc_sample_rate),
                                      "slope": float(slope),
                                      "error": float(error)}
                except Exception as ex:
                    if debug_code:
                        print(str(ex))
                        raise

# yields 0.002 error for bw=3e9, adc_sample_rate=100, slope=6e8
print(f"optimal config: {config}, yields error: {min_error}")

  0%|          | 0/8910 [00:00<?, ?it/s]c:\git\mmWrt\mmWrt\RadarSignalProcessing.py:464: ComplexWarning: Casting complex values to real discards the imaginary part
  cfar_threshold[idx] = mean_noise * threshold_factor
100%|██████████| 8910/8910 [00:19<00:00, 446.70it/s]
optimal config: {'bw': 3000000000.0, 'adc_sample_rate': 8700000.0, 'slope': 4000000000000.0, 'error': 0.0023437499999996447}, yields error: 0.0023437499999996447

[4]:
assert config["bw"] == 3e9
assert config["adc_sample_rate"] == 8.7e6
assert config["slope"] == 4e12
print(mmWrt_ver)
0.0.14.dev1+g243a05bf3.d20260717