Skip to content

xwr.constraints

Radar configuration constraints.

This module documents and enforces known constraints on radar configurations.

Warning

These constraints are not exhaustive or guaranteed to be correct. If you find a missing constraint, or run into an undocumented or incorrectly implemented check, please open an issue!

Summary

Constraint Description
FrameDutyCycle Active transmit time < 99% of frame period
RFDutyCycle RF on-time < 50% of frame period
ExcessRampTime ADC window must complete before ramp ends
CubeSizeLimit Data cube must fit in device L3 buffer
FrameLengthPowerOfTwo Frame length: power of 2
AdcSamplesPowerOfTwo ADC samples: power of 2
MaxSampleRate ADC sample rate ≤ device maximum
MinSampleRate ADC sample rate ≥ device minimum
FrequencyRange Start/end frequency within device RF band
MaxBandwidth Chirp bandwidth ≤ device RF maximum
NetworkUtilization Radar throughput < 80% of capture
ReceiveBuffer Receive buffer must hold ≥ 2 radar frames

xwr.constraints.AdcSamplesPowerOfTwo

Bases: Constraint

ADC samples per chirp must be a power of two.

The number of samples per chirp must be a power of two for the range FFT:

adc_samples & (adc_samples - 1) == 0
Source code in src/xwr/constraints.py
class AdcSamplesPowerOfTwo(Constraint):
    """ADC samples per chirp must be a power of two.

    The number of samples per chirp must be a power of two for the
    range FFT:

        adc_samples & (adc_samples - 1) == 0
    """

    @staticmethod
    def check(radar, capture=None):
        n = radar.adc_samples
        if n == 0:
            return ConstraintCheck(
                AdcSamplesPowerOfTwo, False,
                "adc_samples = 0 (must be nonzero)")

        passed = n & (n - 1) == 0
        detail = f"adc_samples = {n}"
        if not passed:
            detail += " (not a power of two)"
        return ConstraintCheck(AdcSamplesPowerOfTwo, passed, detail)

xwr.constraints.Constraint

Bases: ABC

Base class for a radar configuration constraint.

Source code in src/xwr/constraints.py
class Constraint(ABC):
    """Base class for a radar configuration constraint."""

    @staticmethod
    @abstractmethod
    def check(
        radar: XWRConfig, capture: DCAConfig | None = None
    ) -> "ConstraintCheck":
        """Return a [`ConstraintCheck`][xwr.constraints.ConstraintCheck] result."""

check abstractmethod staticmethod

check(radar: XWRConfig, capture: DCAConfig | None = None) -> ConstraintCheck

Return a ConstraintCheck result.

Source code in src/xwr/constraints.py
@staticmethod
@abstractmethod
def check(
    radar: XWRConfig, capture: DCAConfig | None = None
) -> "ConstraintCheck":
    """Return a [`ConstraintCheck`][xwr.constraints.ConstraintCheck] result."""

xwr.constraints.ConstraintCheck dataclass

Result of a single constraint check.

Attributes:

Name Type Description
constraint type[Constraint]

type of the constraint being checked.

passed bool | None

True if the constraint is satisfied, False if violated, or None if not applicable.

detail str

computed value (on pass/skip) or violation description (on fail).

Source code in src/xwr/constraints.py
@dataclass(frozen=True)
class ConstraintCheck:
    """Result of a single constraint check.

    Attributes:
        constraint: type of the constraint being checked.
        passed: `True` if the constraint is satisfied, `False` if violated, or
            `None` if not applicable.
        detail: computed value (on pass/skip) or violation description
            (on fail).
    """

    constraint: type[Constraint]
    passed: bool | None
    detail: str

xwr.constraints.CubeSizeLimit

Bases: Constraint

Radar data cube must fit within the device L3 radar buffer.

Checks frame_size (total bytes for one frame) against the device hardware L3 memory limit:

frame_size ≤ _LIMITS[device_name]

where frame_size = frame_length × num_tx × num_rx × adc_samples × BYTES_PER_SAMPLE (see XWRConfig.frame_size).

Device L3 size
AWR1642 768 KiB
AWR1843 1 MiB
AWR2944 2.5 MiB
AWRL6844 896 KiB
Source code in src/xwr/constraints.py
class CubeSizeLimit(Constraint):
    """Radar data cube must fit within the device L3 radar buffer.

    Checks `frame_size` (total bytes for one frame) against the device
    hardware L3 memory limit:

        frame_size ≤ _LIMITS[device_name]

    where `frame_size = frame_length × num_tx × num_rx × adc_samples
    × BYTES_PER_SAMPLE` (see [`XWRConfig.frame_size`][xwr.config.XWRConfig]).

    | Device   | L3 size  |
    |----------|----------|
    | AWR1642  | 768 KiB  |
    | AWR1843  | 1 MiB    |
    | AWR2944  | 2.5 MiB  |
    | AWRL6844 | 896 KiB  |
    """

    _LIMITS: ClassVar[dict[str, int]] = {
        "AWR1642":  768 * 1024,
        "AWR1843":  1024 * 1024,
        "AWR1843L": 1024 * 1024,
        "AWR2944":  int(2.5 * 1024 * 1024),
        "AWRL6844": 896 * 1024,
    }

    @staticmethod
    def check(radar, capture=None):
        limit = CubeSizeLimit._LIMITS.get(radar.device_name)
        if limit is None:
            return ConstraintCheck(
                CubeSizeLimit, None,
                f"not checked for {radar.device_name}")
        passed = radar.frame_size <= limit
        detail = (
            f"frame_size = {radar.frame_size} bytes, "
            f"L3 limit = {limit} bytes ({limit // 1024} KiB)")
        if not passed:
            detail = (
                f"frame_size = {radar.frame_size} bytes "
                f"> device L3 limit {limit} bytes ({limit // 1024} KiB)")
        return ConstraintCheck(CubeSizeLimit, passed, detail)

xwr.constraints.ExcessRampTime

Bases: Constraint

ADC sampling must complete before the frequency ramp ends.

The ADC window must fit within the chirp ramp:

ramp_end_time - adc_start_time ≥ T_s

where T_s = adc_samples / sample_rate × 1000 μs (see XWRConfig.sample_time).

Source code in src/xwr/constraints.py
class ExcessRampTime(Constraint):
    """ADC sampling must complete before the frequency ramp ends.

    The ADC window must fit within the chirp ramp:

        ramp_end_time - adc_start_time ≥ T_s

    where `T_s = adc_samples / sample_rate × 1000` μs (see
    [`XWRConfig.sample_time`][xwr.config.XWRConfig]).
    """

    @staticmethod
    def check(radar, capture=None):
        excess = radar.ramp_end_time - radar.adc_start_time - radar.sample_time
        passed = excess >= 0
        detail = f"excess ramp time = {excess:.1f}us"
        if not passed:
            detail += " (must be ≥ 0)"
        return ConstraintCheck(ExcessRampTime, passed, detail)

xwr.constraints.FrameDutyCycle

Bases: Constraint

Active frame time must not exceed the frame period.

The fraction of each period spent actively transmitting must stay below 99%:

frame_time / frame_period < 99%

where frame_time = chirp_time × frame_length / 1000 ms (see XWRConfig.frame_time).

Source code in src/xwr/constraints.py
class FrameDutyCycle(Constraint):
    """Active frame time must not exceed the frame period.

    The fraction of each period spent actively transmitting must stay
    below 99%:

        frame_time / frame_period < 99%

    where `frame_time = chirp_time × frame_length / 1000` ms (see
    [`XWRConfig.frame_time`][xwr.config.XWRConfig]).
    """

    @staticmethod
    def check(radar, capture=None):
        duty_cycle = 100 * radar.frame_time / radar.frame_period
        passed = duty_cycle < 99
        detail = f"frame duty cycle = {duty_cycle:.1f}%"
        if not passed:
            detail += " (must be < 99%)"
        return ConstraintCheck(FrameDutyCycle, passed, detail)

xwr.constraints.FrameLengthPowerOfTwo

Bases: Constraint

Frame length must be a power of two.

The number of chirps per TX antenna per frame must be a power of two for the range-Doppler FFT:

frame_length & (frame_length - 1) == 0
Source code in src/xwr/constraints.py
class FrameLengthPowerOfTwo(Constraint):
    """Frame length must be a power of two.

    The number of chirps per TX antenna per frame must be a power of two
    for the range-Doppler FFT:

        frame_length & (frame_length - 1) == 0
    """

    @staticmethod
    def check(radar, capture=None):
        fl = radar.frame_length
        if fl == 0:
            return ConstraintCheck(
                FrameLengthPowerOfTwo, False,
                "frame_length = 0 (must be nonzero)")

        passed = fl & (fl - 1) == 0
        detail = f"frame_length = {fl}"
        if not passed:
            detail += " (not a power of two)"
        return ConstraintCheck(FrameLengthPowerOfTwo, passed, detail)

xwr.constraints.FrequencyRange

Bases: Constraint

Start and end frequencies must lie within the device RF band.

Both the start frequency and the end frequency (start_freq + bandwidth / 1000) are checked against per-device limits:

Device Min (GHz) Max (GHz)
AWR1642 76 81
AWR1843 76 81
AWR2944 76 81
AWRL6844 57 64
Source code in src/xwr/constraints.py
class FrequencyRange(Constraint):
    """Start and end frequencies must lie within the device RF band.

    Both the start frequency and the end frequency
    (`start_freq + bandwidth / 1000`) are checked against per-device limits:

    | Device   | Min (GHz) | Max (GHz) |
    |----------|-----------|-----------|
    | AWR1642  | 76        | 81        |
    | AWR1843  | 76        | 81        |
    | AWR2944  | 76        | 81        |
    | AWRL6844 | 57        | 64        |
    """

    _LIMITS: ClassVar[dict[str, tuple[float, float]]] = {
        "AWR1642":  (76.0, 81.0),
        "AWR1843":  (76.0, 81.0),
        "AWR1843L": (76.0, 81.0),
        "AWR2944":  (76.0, 81.0),
        "AWRL6844": (57.0, 64.0),
    }

    @staticmethod
    def check(radar, capture=None):
        limits = FrequencyRange._LIMITS.get(radar.device_name)
        if limits is None:
            return ConstraintCheck(
                FrequencyRange, None,
                f"not checked for {radar.device_name}")
        min_freq, max_freq = limits
        start = radar.frequency
        end = radar.frequency + radar.bandwidth / 1000
        if start < min_freq:
            return ConstraintCheck(
                FrequencyRange, False,
                f"start frequency {start:.3f} GHz < device minimum {min_freq} GHz")
        if end > max_freq:
            return ConstraintCheck(
                FrequencyRange, False,
                f"end frequency {end:.3f} GHz > device maximum {max_freq} GHz")
        return ConstraintCheck(
            FrequencyRange, True,
            f"frequency range {start:.3f}{end:.3f} GHz "
            f"(device band {min_freq}{max_freq} GHz)")

xwr.constraints.MaxBandwidth

Bases: Constraint

Effective chirp bandwidth must not exceed the device RF limit.

Bandwidth is computed as freq_slope × T_s (see XWRConfig.bandwidth).

Device Maximum
AWR1642 4000 MHz
AWR1843 4000 MHz
AWR2944 4000 MHz
Source code in src/xwr/constraints.py
class MaxBandwidth(Constraint):
    """Effective chirp bandwidth must not exceed the device RF limit.

    Bandwidth is computed as `freq_slope × T_s` (see
    [`XWRConfig.bandwidth`][xwr.config.XWRConfig]).

    | Device   | Maximum  |
    |----------|----------|
    | AWR1642  | 4000 MHz |
    | AWR1843  | 4000 MHz |
    | AWR2944  | 4000 MHz |
    """

    _LIMITS: ClassVar[dict[str, float]] = {
        "AWR1642":  4000.0,
        "AWR1843":  4000.0,
        "AWR1843L": 4000.0,
        "AWR2944":  4000.0,
    }

    @staticmethod
    def check(radar, capture=None):
        limit = MaxBandwidth._LIMITS.get(radar.device_name)
        if limit is None:
            return ConstraintCheck(
                MaxBandwidth, None,
                f"not checked for {radar.device_name}")
        passed = radar.bandwidth <= limit
        detail = f"bandwidth = {radar.bandwidth:.1f} MHz, maximum = {limit:.0f} MHz"
        if not passed:
            detail = (
                f"bandwidth = {radar.bandwidth:.1f} MHz "
                f"> device maximum {limit:.0f} MHz")
        return ConstraintCheck(MaxBandwidth, passed, detail)

xwr.constraints.MaxSampleRate

Bases: Constraint

ADC sampling rate must not exceed the device maximum.

Device Maximum
AWR1642 12,500 Ksps
AWR1843 25,000 Ksps
AWR2944 37,500 Ksps
AWRL6844 25,000 Ksps
Source code in src/xwr/constraints.py
class MaxSampleRate(Constraint):
    """ADC sampling rate must not exceed the device maximum.

    | Device   | Maximum     |
    |----------|-------------|
    | AWR1642  | 12,500 Ksps |
    | AWR1843  | 25,000 Ksps |
    | AWR2944  | 37,500 Ksps |
    | AWRL6844 | 25,000 Ksps |
    """

    _LIMITS: ClassVar[dict[str, int]] = {
        "AWR1642":  12_500,
        "AWR1843":  25_000,
        "AWR1843L": 25_000,
        "AWR2944":  37_500,
        "AWRL6844": 25_000,
    }

    @staticmethod
    def check(radar, capture=None):
        limit = MaxSampleRate._LIMITS.get(radar.device_name)
        if limit is None:
            return ConstraintCheck(
                MaxSampleRate, None,
                f"not checked for {radar.device_name}")
        passed = radar.sample_rate <= limit
        detail = f"sample_rate = {radar.sample_rate} Ksps, maximum = {limit} Ksps"
        if not passed:
            detail = (
                f"sample_rate = {radar.sample_rate} Ksps "
                f"> device maximum {limit} Ksps")
        return ConstraintCheck(MaxSampleRate, passed, detail)

xwr.constraints.MinSampleRate

Bases: Constraint

ADC sampling rate must meet the device minimum.

Device Minimum
AWR1843 2,000 Ksps
Source code in src/xwr/constraints.py
class MinSampleRate(Constraint):
    """ADC sampling rate must meet the device minimum.

    | Device   | Minimum    |
    |----------|------------|
    | AWR1843  | 2,000 Ksps |
    """

    _LIMITS: ClassVar[dict[str, int]] = {
        "AWR1843":  2_000,
        "AWR1843L": 2_000,
    }

    @staticmethod
    def check(radar, capture=None):
        limit = MinSampleRate._LIMITS.get(radar.device_name)
        if limit is None:
            return ConstraintCheck(
                MinSampleRate, None,
                f"not checked for {radar.device_name}")
        passed = radar.sample_rate >= limit
        detail = f"sample_rate = {radar.sample_rate} Ksps, minimum = {limit} Ksps"
        if not passed:
            detail = (
                f"sample_rate = {radar.sample_rate} Ksps "
                f"< device minimum {limit} Ksps")
        return ConstraintCheck(MinSampleRate, passed, detail)

xwr.constraints.NetworkUtilization

Bases: Constraint

Radar data throughput must not exceed 80% of capture card capacity.

High utilization risks packet loss in the networking:

radar.throughput / capture.throughput < 80%

Skipped if no DCAConfig is provided.

Source code in src/xwr/constraints.py
class NetworkUtilization(Constraint):
    """Radar data throughput must not exceed 80% of capture card capacity.

    High utilization risks packet loss in the networking:

        radar.throughput / capture.throughput < 80%

    Skipped if no [`DCAConfig`][xwr.config.] is provided.
    """

    @staticmethod
    def check(radar, capture=None):
        if capture is None:
            return ConstraintCheck(
                NetworkUtilization, None, "no capture config provided")
        util = 100 * radar.throughput / capture.throughput
        passed = util < 80
        detail = (
            f"network utilization = {util:.1f}% "
            f"(radar {int(radar.throughput / 1e6)} Mbps "
            f"/ capture {int(capture.throughput / 1e6)} Mbps)")
        if not passed:
            detail += " (must be < 80%)"
        return ConstraintCheck(NetworkUtilization, passed, detail)

xwr.constraints.RFDutyCycle

Bases: Constraint

RF transmitter on-time must not exceed 50% of the frame period.

Counts only the time the RF ramp is active (ramp_end_time per TX per chirp), excluding idle time and the inter-frame gap:

ramp_end_time × num_tx × frame_length / (frame_period × 1000) < 50%
Source code in src/xwr/constraints.py
class RFDutyCycle(Constraint):
    """RF transmitter on-time must not exceed 50% of the frame period.

    Counts only the time the RF ramp is active (`ramp_end_time` per TX
    per chirp), excluding idle time and the inter-frame gap:

        ramp_end_time × num_tx × frame_length / (frame_period × 1000) < 50%
    """

    @staticmethod
    def check(radar, capture=None):
        rf_on_us = radar.ramp_end_time * radar.num_tx * radar.frame_length
        period_us = radar.frame_period * 1e3
        duty_cycle = 100 * rf_on_us / period_us
        passed = duty_cycle < 50
        detail = f"RF duty cycle = {duty_cycle:.1f}%"
        if not passed:
            detail += " (should be < 50%)"
        return ConstraintCheck(RFDutyCycle, passed, detail)

xwr.constraints.ReceiveBuffer

Bases: Constraint

OS receive buffer must hold at least two full radar frames.

Since radar frames are transmitted by the capture card in consecutive bursts of packets, a buffer smaller than two frames risks dropping packets when the consumer falls momentarily behind:

socket_buffer / frame_size ≥ 2

Skipped if no DCAConfig is provided.

Source code in src/xwr/constraints.py
class ReceiveBuffer(Constraint):
    """OS receive buffer must hold at least two full radar frames.

    Since radar frames are transmitted by the capture card in consecutive
    bursts of packets, a buffer smaller than two frames risks dropping packets
    when the consumer falls momentarily behind:

        socket_buffer / frame_size ≥ 2

    Skipped if no [`DCAConfig`][xwr.config.] is provided.
    """

    @staticmethod
    def check(radar, capture=None):
        if capture is None:
            return ConstraintCheck(
                ReceiveBuffer, None, "no capture config provided")
        ratio = capture.socket_buffer / radar.frame_size
        passed = ratio >= 2.0
        detail = (
            f"recv buffer = {capture.socket_buffer} bytes "
            f"= {ratio:.2f} frames (1 frame = {radar.frame_size} bytes)"
            + (" (should be >= 2)" if not passed else ""))
        return ConstraintCheck(ReceiveBuffer, passed, detail)

xwr.constraints.check_config

check_config(
    radar: XWRConfig, capture: DCAConfig | None = None, log: bool = True
) -> list[ConstraintCheck]

Run all constraints against a configuration.

Parameters:

Name Type Description Default
radar XWRConfig

radar configuration.

required
capture DCAConfig | None

capture card configuration; cross-config constraints are skipped if not provided.

None
log bool

if True, log each result at INFO level (pass/skip) or WARNING level (fail) using the xwr/constraints logger.

True

Returns:

Type Description
list[ConstraintCheck]

All constraint results, including passed and skipped checks.

Source code in src/xwr/constraints.py
def check_config(
    radar: XWRConfig,
    capture: DCAConfig | None = None,
    log: bool = True,
) -> list[ConstraintCheck]:
    """Run all constraints against a configuration.

    Args:
        radar: radar configuration.
        capture: capture card configuration; cross-config constraints are
            skipped if not provided.
        log: if `True`, log each result at INFO level (pass/skip) or WARNING
            level (fail) using the `xwr/constraints` logger.

    Returns:
        All constraint results, including passed and skipped checks.
    """
    CONSTRAINTS: list[type[Constraint]] = [
        FrameDutyCycle,
        RFDutyCycle,
        ExcessRampTime,
        CubeSizeLimit,
        FrameLengthPowerOfTwo,
        AdcSamplesPowerOfTwo,
        MaxSampleRate,
        MinSampleRate,
        FrequencyRange,
        MaxBandwidth,
        NetworkUtilization,
        ReceiveBuffer,
    ]
    results = [C.check(radar, capture) for C in CONSTRAINTS]
    if log:
        logger = logging.getLogger("xwr/constraints")
        for r in results:
            name = r.constraint.__name__
            if r.passed is False:
                logger.warning(f"Possibly invalid - {name}: {r.detail}")
            else:
                logger.info(
                    f"{'skipped' if r.passed is None else 'pass'}"
                    f" | {name}: {r.detail}")
    return results