Skip to content

Radar Models API

Radar equation and detection probability calculations.

Overview

from phased_array_systems.models.radar import (
    RadarModel,
    compute_detection_threshold,
    compute_pd_from_snr,
    compute_snr_for_pd,
    albersheim_snr,
    coherent_integration_gain,
    noncoherent_integration_gain,
    integration_loss,
)

Classes

RadarModel

Radar range equation calculator.

Implements the monostatic radar range equation:

P_r = (P_t * G^2 * λ^2 * σ) / ((4π)^3 * R^4 * L_sys)
Or in dB form

SNR = P_t + 2G + 2λ_dB + σ_dBsm - 4*R_dB - L_sys - (4π)^3_dB - N_dB

Where

P_t = Peak transmit power (W) G = Antenna gain (same for Tx/Rx in monostatic) λ = Wavelength (m) σ = Target radar cross section (m^2) R = Range to target (m) L_sys = System losses N = Noise power = kTB

ATTRIBUTE DESCRIPTION
name

Model block name for identification

TYPE: str

evaluate

evaluate(arch: Architecture, scenario: RadarDetectionScenario, context: dict[str, Any]) -> MetricsDict

Evaluate radar detection performance.

PARAMETER DESCRIPTION
arch

Architecture configuration

TYPE: Architecture

scenario

Radar detection scenario

TYPE: RadarDetectionScenario

context

Additional context (may include antenna metrics): - g_peak_db: Antenna gain (uses this if provided) - scan_loss_db: Scan loss (uses this if provided)

TYPE: dict[str, Any]

RETURNS DESCRIPTION
MetricsDict

Dictionary with radar metrics: - peak_power_w: Peak transmit power (W) - peak_power_dbw: Peak transmit power (dBW) - g_ant_db: Antenna gain (dB) - wavelength_m: Wavelength (m) - target_rcs_dbsm: Target RCS (dBsm) - target_rcs_m2: Target RCS (m^2) - range_m: Target range (m) - noise_power_dbw: Noise power (dBW) - snr_single_pulse_db: Single-pulse SNR (dB) - integration_gain_db: Integration gain (dB) - snr_integrated_db: Integrated SNR (dB) - snr_required_db: Required SNR for Pd/Pfa (dB) - snr_margin_db: SNR margin (dB) - pd_achieved: Achieved probability of detection - detection_range_m: Max detection range for required Pd (m)

Source code in src/phased_array_systems/models/radar/equation.py
def evaluate(
    self,
    arch: Architecture,
    scenario: RadarDetectionScenario,
    context: dict[str, Any],
) -> MetricsDict:
    """Evaluate radar detection performance.

    Args:
        arch: Architecture configuration
        scenario: Radar detection scenario
        context: Additional context (may include antenna metrics):
            - g_peak_db: Antenna gain (uses this if provided)
            - scan_loss_db: Scan loss (uses this if provided)

    Returns:
        Dictionary with radar metrics:
            - peak_power_w: Peak transmit power (W)
            - peak_power_dbw: Peak transmit power (dBW)
            - g_ant_db: Antenna gain (dB)
            - wavelength_m: Wavelength (m)
            - target_rcs_dbsm: Target RCS (dBsm)
            - target_rcs_m2: Target RCS (m^2)
            - range_m: Target range (m)
            - noise_power_dbw: Noise power (dBW)
            - snr_single_pulse_db: Single-pulse SNR (dB)
            - integration_gain_db: Integration gain (dB)
            - snr_integrated_db: Integrated SNR (dB)
            - snr_required_db: Required SNR for Pd/Pfa (dB)
            - snr_margin_db: SNR margin (dB)
            - pd_achieved: Achieved probability of detection
            - detection_range_m: Max detection range for required Pd (m)
    """
    # Get antenna gain from context or compute approximate
    if "g_peak_db" in context:
        g_ant_db = context["g_peak_db"]
        # Apply scan loss if provided
        if "scan_loss_db" in context:
            g_ant_db -= context["scan_loss_db"]
    else:
        # Approximate gain for uniform rectangular array
        # G ≈ 4*pi*A/λ^2 = 4*pi * (nx*dx) * (ny*dy) when spacing in wavelengths
        aperture_lambda_sq = (
            arch.array.nx * arch.array.dx_lambda * arch.array.ny * arch.array.dy_lambda
        )
        g_ant_linear = 4 * math.pi * aperture_lambda_sq
        g_ant_db = 10 * math.log10(g_ant_linear)

    # Transmit power (peak)
    n_elements = arch.array.n_elements
    peak_power_w = arch.rf.tx_power_w_per_elem * n_elements
    peak_power_dbw = W_TO_DBW(peak_power_w)

    # Wavelength
    wavelength_m = C_LIGHT / scenario.freq_hz
    wavelength_db = 10 * math.log10(wavelength_m)

    # System losses (feed network + additional system losses)
    system_loss_db = arch.rf.feed_loss_db + arch.rf.system_loss_db

    # Target RCS
    rcs_dbsm = scenario.target_rcs_dbsm
    rcs_m2 = 10 ** (rcs_dbsm / 10)

    # Range
    range_m = scenario.range_m
    range_db = 10 * math.log10(range_m)

    # Noise power: N = kTB
    noise_temp_k = scenario.rx_noise_temp_k
    noise_power_w = K_B * noise_temp_k * scenario.bandwidth_hz
    noise_power_dbw = W_TO_DBW(noise_power_w) + arch.rf.noise_figure_db

    # Radar equation constant: (4π)^3 in dB
    radar_constant_db = 30 * math.log10(4 * math.pi)  # ≈ 32.98 dB

    # Single-pulse SNR (monostatic radar equation in dB)
    # SNR = Pt + 2*G + 2*λ_dB + σ - 4*R_dB - L - (4π)^3_dB - N
    snr_single_db = (
        peak_power_dbw
        + 2 * g_ant_db
        + 2 * wavelength_db
        + rcs_dbsm
        - 4 * range_db
        - system_loss_db
        - radar_constant_db
        - noise_power_dbw
    )

    # Integration gain
    n_pulses = scenario.n_pulses
    if scenario.integration_type == "coherent":
        integration_gain_db = coherent_integration_gain(n_pulses)
    else:
        integration_gain_db = noncoherent_integration_gain(
            n_pulses, pd=scenario.pd_required, pfa=scenario.pfa
        )

    # Integrated SNR
    snr_integrated_db = snr_single_db + integration_gain_db

    # Required SNR for Pd/Pfa using Albersheim's equation
    snr_required_db = albersheim_snr(
        pd=scenario.pd_required,
        pfa=scenario.pfa,
        n_pulses=1,  # Integration gain already applied
    )

    # SNR margin
    snr_margin_db = snr_integrated_db - snr_required_db

    # Achieved Pd at the given range
    pd_achieved = compute_pd_from_snr(
        snr_integrated_db,
        scenario.pfa,
        swerling=0,  # Non-fluctuating
        n_pulses=1,  # Already integrated
        integration="coherent",  # SNR already includes integration
    )

    # Detection range (range where margin = 0)
    # From radar equation: R^4 proportional to SNR
    # R_det / R = (SNR_integrated / SNR_required)^(1/4)
    # In dB: R_det = R * 10^(margin_dB / 40)
    detection_range_m = range_m * 10 ** (snr_margin_db / 40) if snr_margin_db > -40 else 0.0

    return {
        # Power
        "peak_power_w": peak_power_w,
        "peak_power_dbw": peak_power_dbw,
        # Antenna
        "g_ant_db": g_ant_db,
        # Target/Environment
        "wavelength_m": wavelength_m,
        "target_rcs_dbsm": rcs_dbsm,
        "target_rcs_m2": rcs_m2,
        "range_m": range_m,
        # Noise
        "noise_power_dbw": noise_power_dbw,
        "system_loss_db": system_loss_db,
        # SNR
        "snr_single_pulse_db": snr_single_db,
        "integration_gain_db": integration_gain_db,
        "snr_integrated_db": snr_integrated_db,
        "snr_required_db": snr_required_db,
        "snr_margin_db": snr_margin_db,
        # Detection
        "pd_achieved": pd_achieved,
        "pd_required": scenario.pd_required,
        "pfa": scenario.pfa,
        "n_pulses": n_pulses,
        "integration_type": scenario.integration_type,
        "detection_range_m": detection_range_m,
    }

Functions

compute_snr_for_pd

compute_snr_for_pd(pd: float, pfa: float, swerling: SwerlingModel = 0, n_pulses: int = 1, integration: Literal['coherent', 'noncoherent'] = 'noncoherent') -> float

Compute required SNR for given Pd and Pfa.

Inverse of compute_pd_from_snr using numerical root finding.

PARAMETER DESCRIPTION
pd

Required probability of detection (0 < pd < 1)

TYPE: float

pfa

Probability of false alarm (0 < pfa < 1)

TYPE: float

swerling

Swerling target model (0-4)

TYPE: SwerlingModel DEFAULT: 0

n_pulses

Number of pulses integrated

TYPE: int DEFAULT: 1

integration

Integration type

TYPE: Literal['coherent', 'noncoherent'] DEFAULT: 'noncoherent'

RETURNS DESCRIPTION
float

Required single-pulse SNR in dB

Source code in src/phased_array_systems/models/radar/detection.py
def compute_snr_for_pd(
    pd: float,
    pfa: float,
    swerling: SwerlingModel = 0,
    n_pulses: int = 1,
    integration: Literal["coherent", "noncoherent"] = "noncoherent",
) -> float:
    """Compute required SNR for given Pd and Pfa.

    Inverse of compute_pd_from_snr using numerical root finding.

    Args:
        pd: Required probability of detection (0 < pd < 1)
        pfa: Probability of false alarm (0 < pfa < 1)
        swerling: Swerling target model (0-4)
        n_pulses: Number of pulses integrated
        integration: Integration type

    Returns:
        Required single-pulse SNR in dB
    """
    if not 0 < pd < 1:
        raise ValueError("pd must be between 0 and 1")
    if not 0 < pfa < 1:
        raise ValueError("pfa must be between 0 and 1")

    def objective(snr_db: float) -> float:
        pd_calc = compute_pd_from_snr(snr_db, pfa, swerling, n_pulses, integration)
        return pd_calc - pd

    # Use Albersheim as initial guess
    snr_guess = albersheim_snr(pd, pfa, n_pulses)

    try:
        result = optimize.brentq(objective, snr_guess - 20, snr_guess + 20)
        return result
    except ValueError:
        # If brentq fails, return Albersheim estimate
        return snr_guess

compute_pd_from_snr

compute_pd_from_snr(snr_db: float, pfa: float, swerling: SwerlingModel = 0, n_pulses: int = 1, integration: Literal['coherent', 'noncoherent'] = 'noncoherent') -> float

Compute probability of detection for given SNR.

Uses Marcum Q-function for Swerling 0 (non-fluctuating) targets.

PARAMETER DESCRIPTION
snr_db

Signal-to-noise ratio per pulse (dB)

TYPE: float

pfa

Probability of false alarm

TYPE: float

swerling

Swerling target model (0 = non-fluctuating)

TYPE: SwerlingModel DEFAULT: 0

n_pulses

Number of pulses integrated

TYPE: int DEFAULT: 1

integration

Integration type ("coherent" or "noncoherent")

TYPE: Literal['coherent', 'noncoherent'] DEFAULT: 'noncoherent'

RETURNS DESCRIPTION
float

Probability of detection (0-1)

Source code in src/phased_array_systems/models/radar/detection.py
def compute_pd_from_snr(
    snr_db: float,
    pfa: float,
    swerling: SwerlingModel = 0,
    n_pulses: int = 1,
    integration: Literal["coherent", "noncoherent"] = "noncoherent",
) -> float:
    """Compute probability of detection for given SNR.

    Uses Marcum Q-function for Swerling 0 (non-fluctuating) targets.

    Args:
        snr_db: Signal-to-noise ratio per pulse (dB)
        pfa: Probability of false alarm
        swerling: Swerling target model (0 = non-fluctuating)
        n_pulses: Number of pulses integrated
        integration: Integration type ("coherent" or "noncoherent")

    Returns:
        Probability of detection (0-1)
    """
    if not 0 < pfa < 1:
        raise ValueError("pfa must be between 0 and 1")

    snr_linear = 10 ** (snr_db / 10)

    # Apply integration gain
    if integration == "coherent":
        # Coherent integration: SNR scales linearly with n
        snr_integrated = snr_linear * n_pulses
    else:
        # Non-coherent integration: approximate gain
        # Using empirical formula: effective SNR ≈ snr * n^0.8
        snr_integrated = snr_linear * (n_pulses ** 0.8)

    # Compute threshold from Pfa
    threshold = compute_detection_threshold(pfa, n_samples=1)

    # For Swerling 0 (non-fluctuating), use Marcum Q-function approximation
    # Pd = Q(sqrt(2*SNR), sqrt(2*threshold))
    # Using Rice distribution approximation
    if swerling == 0:
        # Marcum Q-function: Q_1(a, b) where a = sqrt(2*SNR), b = sqrt(threshold)
        a = math.sqrt(2 * snr_integrated)
        b = math.sqrt(2 * threshold)

        # Approximate Marcum Q using modified Bessel function
        # For high SNR, Pd ≈ 1 - 0.5 * erfc((a - b) / sqrt(2))
        if a > b:
            pd = 0.5 * special.erfc((b - a) / math.sqrt(2))
        else:
            pd = 0.5 * special.erfc((b - a) / math.sqrt(2))

        # Clamp to valid range
        return max(0.0, min(1.0, pd))

    elif swerling in (1, 2, 3, 4):
        # Swerling models with fluctuating RCS
        # Use empirical adjustment factors
        if swerling == 1:
            # Slow fluctuation, Rayleigh
            factor = 1.0 + 1.0 / snr_integrated if snr_integrated > 0 else 0
        elif swerling == 2:
            # Fast fluctuation, Rayleigh
            factor = 1.0 + 0.5 / snr_integrated if snr_integrated > 0 else 0
        elif swerling == 3:
            # Slow fluctuation, chi-squared (4 DOF)
            factor = 1.0 + 2.0 / snr_integrated if snr_integrated > 0 else 0
        else:  # swerling == 4
            # Fast fluctuation, chi-squared (4 DOF)
            factor = 1.0 + 1.0 / snr_integrated if snr_integrated > 0 else 0

        # Adjusted threshold
        adj_snr = snr_integrated / factor if factor > 0 else snr_integrated
        a = math.sqrt(2 * adj_snr)
        b = math.sqrt(2 * threshold)
        pd = 0.5 * special.erfc((b - a) / math.sqrt(2))
        return max(0.0, min(1.0, pd))

    else:
        raise ValueError(f"Unknown Swerling model: {swerling}")

albersheim_snr

albersheim_snr(pd: float, pfa: float, n_pulses: int = 1) -> float

Albersheim's equation for required SNR (Swerling 0).

Empirical approximation valid for: - 0.1 <= Pd <= 0.99 - 1e-9 <= Pfa <= 1e-3 - 1 <= n_pulses <= 8096

PARAMETER DESCRIPTION
pd

Probability of detection

TYPE: float

pfa

Probability of false alarm

TYPE: float

n_pulses

Number of pulses (non-coherent integration)

TYPE: int DEFAULT: 1

RETURNS DESCRIPTION
float

Required single-pulse SNR in dB

Source code in src/phased_array_systems/models/radar/detection.py
def albersheim_snr(
    pd: float,
    pfa: float,
    n_pulses: int = 1,
) -> float:
    """Albersheim's equation for required SNR (Swerling 0).

    Empirical approximation valid for:
    - 0.1 <= Pd <= 0.99
    - 1e-9 <= Pfa <= 1e-3
    - 1 <= n_pulses <= 8096

    Args:
        pd: Probability of detection
        pfa: Probability of false alarm
        n_pulses: Number of pulses (non-coherent integration)

    Returns:
        Required single-pulse SNR in dB
    """
    if not 0.1 <= pd <= 0.9999:
        raise ValueError("pd must be between 0.1 and 0.9999 for Albersheim")
    if not 1e-10 <= pfa <= 0.1:
        raise ValueError("pfa must be between 1e-10 and 0.1 for Albersheim")
    if n_pulses < 1:
        raise ValueError("n_pulses must be >= 1")

    # Albersheim's equation
    A = math.log(0.62 / pfa)
    B = math.log(pd / (1 - pd))

    # SNR required for n pulses (non-coherent integration)
    snr_n_db = -5 * math.log10(n_pulses) + (6.2 + 4.54 / math.sqrt(n_pulses + 0.44)) * math.log10(
        A + 0.12 * A * B + 1.7 * B
    )

    return snr_n_db

coherent_integration_gain

coherent_integration_gain(n_pulses: int) -> float

Coherent integration gain in dB.

Coherent integration (phase-preserving) provides full N-times improvement in SNR because signals add coherently while noise adds incoherently.

PARAMETER DESCRIPTION
n_pulses

Number of pulses integrated (must be >= 1)

TYPE: int

RETURNS DESCRIPTION
float

Integration gain in dB: 10 * log10(n_pulses)

RAISES DESCRIPTION
ValueError

If n_pulses < 1

Source code in src/phased_array_systems/models/radar/integration.py
def coherent_integration_gain(n_pulses: int) -> float:
    """Coherent integration gain in dB.

    Coherent integration (phase-preserving) provides full N-times
    improvement in SNR because signals add coherently while noise
    adds incoherently.

    Args:
        n_pulses: Number of pulses integrated (must be >= 1)

    Returns:
        Integration gain in dB: 10 * log10(n_pulses)

    Raises:
        ValueError: If n_pulses < 1
    """
    if n_pulses < 1:
        raise ValueError("n_pulses must be >= 1")

    if n_pulses == 1:
        return 0.0

    return 10 * math.log10(n_pulses)

noncoherent_integration_gain

noncoherent_integration_gain(n_pulses: int, pd: float = 0.9, pfa: float = 1e-06) -> float

Non-coherent integration gain in dB.

Non-coherent integration (magnitude-only) provides less than full N-times gain because both signal and noise magnitudes are combined. The efficiency depends on SNR and Pd/Pfa.

Uses empirical approximation: gain ≈ 10 * log10(n^efficiency) where efficiency ≈ 0.8 for typical radar parameters.

PARAMETER DESCRIPTION
n_pulses

Number of pulses integrated (must be >= 1)

TYPE: int

pd

Probability of detection (affects efficiency)

TYPE: float DEFAULT: 0.9

pfa

Probability of false alarm (affects efficiency)

TYPE: float DEFAULT: 1e-06

RETURNS DESCRIPTION
float

Integration gain in dB (always <= coherent gain)

RAISES DESCRIPTION
ValueError

If n_pulses < 1

Source code in src/phased_array_systems/models/radar/integration.py
def noncoherent_integration_gain(
    n_pulses: int,
    pd: float = 0.9,
    pfa: float = 1e-6,
) -> float:
    """Non-coherent integration gain in dB.

    Non-coherent integration (magnitude-only) provides less than
    full N-times gain because both signal and noise magnitudes
    are combined. The efficiency depends on SNR and Pd/Pfa.

    Uses empirical approximation: gain ≈ 10 * log10(n^efficiency)
    where efficiency ≈ 0.8 for typical radar parameters.

    Args:
        n_pulses: Number of pulses integrated (must be >= 1)
        pd: Probability of detection (affects efficiency)
        pfa: Probability of false alarm (affects efficiency)

    Returns:
        Integration gain in dB (always <= coherent gain)

    Raises:
        ValueError: If n_pulses < 1
    """
    if n_pulses < 1:
        raise ValueError("n_pulses must be >= 1")

    if n_pulses == 1:
        return 0.0

    # Efficiency factor depends on operating point
    # Higher Pd requires higher SNR, reducing integration efficiency
    if pd >= 0.99:
        efficiency = 0.7
    elif pd >= 0.9:
        efficiency = 0.8
    elif pd >= 0.5:
        efficiency = 0.85
    else:
        efficiency = 0.9

    # Non-coherent gain: approximately n^efficiency
    return 10 * efficiency * math.log10(n_pulses)

Output Metrics

Metric Units Description
snr_single_pulse_db dB Single-pulse SNR
snr_integrated_db dB SNR after integration
snr_required_db dB Required SNR for Pd/Pfa
snr_margin_db dB Margin above required
detection_range_m m Maximum detection range
integration_gain_db dB Gain from pulse integration

Usage Examples

Using RadarModel

from phased_array_systems.models.radar import RadarModel
from phased_array_systems.scenarios import RadarDetectionScenario

scenario = RadarDetectionScenario(
    freq_hz=10e9,
    target_rcs_m2=1.0,
    range_m=100e3,
    required_pd=0.9,
    pfa=1e-6,
    pulse_width_s=10e-6,
    prf_hz=1000,
    n_pulses=10,
    integration_type="coherent",
    swerling_model=1,
)

model = RadarModel()
metrics = model.evaluate(arch, scenario, context={})

print(f"Single-Pulse SNR: {metrics['snr_single_pulse_db']:.1f} dB")
print(f"Integrated SNR: {metrics['snr_integrated_db']:.1f} dB")
print(f"SNR Margin: {metrics['snr_margin_db']:.1f} dB")

Computing Required SNR

from phased_array_systems.models.radar import compute_snr_for_pd

snr_req = compute_snr_for_pd(
    pd=0.9,
    pfa=1e-6,
    swerling_model=1,
)
print(f"Required SNR: {snr_req:.1f} dB")

Computing Detection Probability

from phased_array_systems.models.radar import compute_pd_from_snr

pd = compute_pd_from_snr(
    snr_db=15.0,
    pfa=1e-6,
    swerling_model=1,
)
print(f"Detection Probability: {pd:.3f}")

Integration Gain

from phased_array_systems.models.radar import coherent_integration_gain, noncoherent_integration_gain

# Coherent integration
gain_coherent = coherent_integration_gain(n_pulses=16)
print(f"Coherent Gain: {gain_coherent:.1f} dB")  # 12.0 dB

# Non-coherent integration
gain_noncoherent = noncoherent_integration_gain(n_pulses=16)
print(f"Non-coherent Gain: {gain_noncoherent:.1f} dB")  # ~6.0 dB

Radar Range Equation

\[ SNR = \frac{P_t G^2 \lambda^2 \sigma}{(4\pi)^3 R^4 k T_s B_n L_s} \]

Where:

  • \(P_t\) = Peak transmit power (W)
  • \(G\) = Antenna gain (linear)
  • \(\lambda\) = Wavelength (m)
  • \(\sigma\) = Target RCS (m²)
  • \(R\) = Target range (m)
  • \(k\) = Boltzmann constant
  • \(T_s\) = System noise temperature (K)
  • \(B_n\) = Noise bandwidth (Hz)
  • \(L_s\) = System losses (linear)

Swerling Models

Model PDF Decorrelation
0 Constant None
1 Rayleigh Scan-to-scan
2 Rayleigh Pulse-to-pulse
3 Chi-squared (4 DOF) Scan-to-scan
4 Chi-squared (4 DOF) Pulse-to-pulse

See Also