From 42452f32bd2b97cdc0dccadc8ecacdd97d8c4c67 Mon Sep 17 00:00:00 2001 From: vishwaksen-1 Date: Thu, 5 Feb 2026 21:46:11 +0530 Subject: [PATCH 1/3] "Add real-time packet detection section to detection chapter" --- _images/detection_realtime.svg | 95912 ++++++++++++++++ content/detection.rst | 560 + .../detection_realtime.py | 154 + 3 files changed, 96626 insertions(+) create mode 100644 _images/detection_realtime.svg create mode 100644 figure-generating-scripts/detection_realtime.py diff --git a/_images/detection_realtime.svg b/_images/detection_realtime.svg new file mode 100644 index 00000000..cd1c80d9 --- /dev/null +++ b/_images/detection_realtime.svg @@ -0,0 +1,95912 @@ + + + + + + + + 2026-02-05T20:17:37.262558 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/detection.rst b/content/detection.rst index b9cf5eff..c8b6e591 100644 --- a/content/detection.rst +++ b/content/detection.rst @@ -410,3 +410,563 @@ In a DSSS system, the receiver's ability to recover data is entirely dependent o :alt: DSSS The peak occurs at zero offset as expected, and it drops linearly, i.e. it gets to half the peak value at a half-chip offset. After more than one chip offset the correlation might seem like it's going back up, but the actual peak will be low because it's not aligned to the sequence anymore. + +**************************************************** +Real-Time Packet Detection in Continuous IQ Streams +**************************************************** + +So far we have explored the theoretical foundations of signal detection, from correlators to CFAR detectors to spread spectrum systems. Now we bring it all together to solve a common practical problem: **detecting intermittent packets in a continuous stream of IQ samples from an SDR**. + +The Challenge +############# + +Consider this scenario: You have a modem or IoT device that transmits a data packet once per second (or at irregular intervals). Your SDR is continuously receiving samples at, say, 1 MHz. The packets arrive at unpredictable times, buried in noise and interference. You need to: + +1. Detect when a packet arrives +2. Determine the exact sample index where it starts +3. Extract the packet for further processing (demodulation, decoding, etc.) +4. Do this in real-time without missing packets + +This is fundamentally different from processing a pre-recorded IQ file where you can analyze the entire signal at once. Here, samples arrive continuously, and you must make decisions in real-time with limited computational resources. + +Real-World Applications +####################### + +This pattern appears everywhere in wireless systems: + +- **IoT Sensor Networks**: LoRa, Sigfox, and NB-IoT devices that wake up periodically to transmit sensor data +- **Amateur Radio Packet Networks**: APRS beacons transmitting GPS position reports +- **Satellite Communications**: LEO satellites passing overhead transmitting telemetry bursts +- **Industrial Wireless**: Factory sensors reporting only when measurements exceed thresholds +- **Smart Home Devices**: Zigbee, Z-Wave devices transmitting when button pressed or state changes + +In all these cases, the receiver must remain vigilant, detecting packets when they arrive without knowing precisely when that will be. + +The Building Blocks +################### + +We will combine several techniques covered in this chapter: + +1. **Cross-Correlation**: To find the known preamble pattern +2. **CFAR Detection**: To adaptively set thresholds despite varying noise +3. **Buffer Management**: To handle continuous streaming data +4. **Peak Detection**: To extract precise packet timing + +The key insight is that we don't need to process every single sample individually. Instead, we: +- Accumulate samples in **buffers** (chunks of, say, 100,000 samples) +- Run our detector on each buffer +- Extract detected packets +- Maintain state across buffer boundaries to avoid missing packets that span buffers + +Implementation Strategy +####################### + +Our detector will follow this workflow: + +.. code-block:: text + + ┌─────────────────────────────────────────────────────────────┐ + │ Continuous IQ Stream from SDR (e.g., 1 Msps) │ + └────────────────────┬────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ Buffer Accumulation (e.g., 100k samples = 0.1 sec) │ + └────────────────────┬────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ Cross-Correlation with Known Preamble │ + │ → Produces correlation vs. sample index │ + └────────────────────┬────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ CFAR Threshold Computation │ + │ → Adaptive threshold that tracks noise floor │ + └────────────────────┬────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ Peak Detection (correlation > threshold) │ + │ → List of candidate packet start indices │ + └────────────────────┬────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────────────────────┐ + │ Packet Extraction & Validation │ + │ → Extract samples, pass to demodulator │ + └─────────────────────────────────────────────────────────────┘ + +**Buffer Overlap Strategy** + +To avoid missing packets that straddle buffer boundaries, we use an **overlap-save** approach: + +- Each buffer includes the last ``N_preamble`` samples from the previous buffer +- This ensures any packet starting near the end of buffer ``i`` will be fully contained in buffer ``i+1`` +- Trade-off: Small computational overhead vs. guaranteed detection + +Python Implementation +##################### + +Let's build a complete packet detector step by step. We'll use a Zadoff-Chu preamble (as introduced earlier) and implement adaptive CFAR detection. + +Step 1: Define the Preamble and Parameters +******************************************* + +.. code-block:: python + + import numpy as np + import matplotlib.pyplot as plt + from scipy.signal import correlate + + # Preamble: Zadoff-Chu sequence (excellent correlation properties) + N_zc = 63 # ZC sequence length (typically prime or power of 2 - 1) + u = 5 # ZC root + t = np.arange(N_zc) + preamble = np.exp(-1j * np.pi * u * t * (t + 1) / N_zc) + + # System parameters + sample_rate = 1e6 + buffer_size = 100000 + overlap_size = len(preamble) # Overlap to catch boundary packets + + # CFAR parameters + cfar_guard = 10 + cfar_train = 50 + pfa_target = 1e-6 + + # Packet parameters (for simulation) + packet_length = 500 # Total packet length in samples (preamble + data) + snr_db = -5 + +Step 2: CFAR Detector Function +******************************* + +We'll use the Cell-Averaging CFAR (CA-CFAR) from earlier, slightly optimized: + +.. code-block:: python + + def ca_cfar_1d(signal, num_train, num_guard, pfa): + """ + 1D Cell-Averaging CFAR detector. + + Args: + signal: Input signal (typically correlation magnitude) + num_train: Number of training cells (on each side) + num_guard: Number of guard cells (on each side) + pfa: Target probability of false alarm + + Returns: + threshold: Adaptive threshold array + """ + n = len(signal) + threshold = np.zeros(n) + alpha = num_train * (pfa**(-1/num_train) - 1) + + for i in range(n): + # Define training window indices + train_start_left = max(0, i - num_guard - num_train) + train_end_left = max(0, i - num_guard) + train_start_right = min(n, i + num_guard + 1) + train_end_right = min(n, i + num_guard + num_train + 1) + + # Collect training cells (avoid guard cells and CUT) + train_cells = np.concatenate([ + signal[train_start_left:train_end_left], + signal[train_start_right:train_end_right] + ]) + + if len(train_cells) > 0: + noise_est = np.mean(train_cells) + threshold[i] = alpha * noise_est + + return threshold + +Step 3: Packet Detection Function +********************************** + +.. code-block:: python + + def detect_packets(buffer, preamble, cfar_guard, cfar_train, pfa, + min_spacing=None): + """ + Detect packets in a buffer of IQ samples. + + Args: + buffer: Complex IQ samples + preamble: Known preamble sequence + cfar_guard: CFAR guard cells + cfar_train: CFAR training cells + pfa: Target false alarm probability + min_spacing: Minimum samples between detections (prevents duplicates) + + Returns: + detections: List of sample indices where packets start + """ + # Correlate buffer with preamble + corr = correlate(buffer, preamble, mode='same') + corr_power = np.abs(corr)**2 + + # Compute adaptive threshold + threshold = ca_cfar_1d(corr_power, cfar_train, cfar_guard, pfa) + + # Find peaks above threshold + detections_raw = np.where(corr_power > threshold)[0] + + # Compensate for correlation offset (peak occurs at len(preamble)//2 after true start) + half_preamble = len(preamble) // 2 + detections_raw = detections_raw - half_preamble + + # Remove edge detections (unreliable) + half_preamble = len(preamble) // 2 + detections_raw = detections_raw[ + (detections_raw > half_preamble) & + (detections_raw < len(buffer) - half_preamble) + ] + + # Remove duplicate detections (peaks close together) + if min_spacing is None: + min_spacing = len(preamble) + + detections = [] + if len(detections_raw) > 0: + detections.append(detections_raw[0]) + for det in detections_raw[1:]: + if det - detections[-1] > min_spacing: + detections.append(det) + + return detections, corr_power, threshold + +Step 4: Simulation - Generate Test Signal +****************************************** + +.. code-block:: python + + def generate_packet_stream(preamble, packet_length, num_packets, + sample_rate, snr_db): + """ + Generate a simulated IQ stream with intermittent packets. + + Returns: + signal: Complex IQ samples + true_starts: Ground truth packet start indices + """ + # Calculate noise power from SNR + signal_power = 1.0 # Normalized preamble power + noise_power = signal_power / (10**(snr_db/10)) + noise_std = np.sqrt(noise_power / 2) # Complex noise + + # Generate QPSK data (random payload after preamble) + qpsk_map = np.array([1+1j, -1+1j, -1-1j, 1-1j]) / np.sqrt(2) + + # Time between packets (1 second +/- 20% jitter) + packets_per_sec = 1 + avg_gap = int(sample_rate / packets_per_sec) + + signal = [] + true_starts = [] + + for i in range(num_packets): + # Add gap (noise only) + if i == 0: + gap_length = np.random.randint(avg_gap//2, avg_gap) + else: + gap_length = np.random.randint(int(avg_gap*0.8), int(avg_gap*1.2)) + + noise = noise_std * (np.random.randn(gap_length) + + 1j*np.random.randn(gap_length)) + signal.extend(noise) + + # Record true packet start + true_starts.append(len(signal)) + + # Add packet (preamble + data) + data_length = packet_length - len(preamble) + data = qpsk_map[np.random.randint(0, 4, data_length)] + packet = np.concatenate([preamble, data]) + + # Add noise to packet + packet_noisy = packet + noise_std * (np.random.randn(len(packet)) + + 1j*np.random.randn(len(packet))) + signal.extend(packet_noisy) + + # Add final gap + gap_length = np.random.randint(avg_gap//2, avg_gap) + noise = noise_std * (np.random.randn(gap_length) + + 1j*np.random.randn(gap_length)) + signal.extend(noise) + + return np.array(signal), true_starts + + # Generate 5 seconds of signal with ~5 packets + signal, true_starts = generate_packet_stream( + preamble, packet_length, num_packets=5, + sample_rate=sample_rate, snr_db=snr_db + ) + + print(f"Generated {len(signal)} samples ({len(signal)/sample_rate:.1f} sec)") + print(f"True packet starts: {true_starts}") + +Step 5: Run Detection in Streaming Mode +**************************************** + +Now we process the signal in chunks, simulating real-time streaming: + +.. code-block:: python + + def process_stream(signal, preamble, buffer_size, overlap_size, + cfar_guard, cfar_train, pfa): + """ + Process continuous IQ stream in buffers (simulates real-time). + + Returns: + all_detections: List of detected packet starts (global indices) + """ + all_detections = [] + n_samples = len(signal) + current_pos = 0 + + while current_pos < n_samples: + # Define buffer with overlap + buffer_start = max(0, current_pos - overlap_size) + buffer_end = min(n_samples, current_pos + buffer_size) + buffer = signal[buffer_start:buffer_end] + + # Detect packets in this buffer + detections, corr_power, threshold = detect_packets( + buffer, preamble, cfar_guard, cfar_train, pfa + ) + + # Convert buffer-relative indices to global indices + for det in detections: + global_idx = buffer_start + det + + # Avoid duplicate detections from overlap region + if len(all_detections) == 0 or \ + global_idx - all_detections[-1] > len(preamble): + all_detections.append(global_idx) + + current_pos += buffer_size + + return all_detections + + + detected_starts = process_stream( + signal, preamble, buffer_size, overlap_size, + cfar_guard, cfar_train, pfa_target + ) + + print(f"\nDetection Results:") + print(f"True packets: {len(true_starts)}") + print(f"Detected packets: {len(detected_starts)}") + print(f"Detected starts: {detected_starts}") + +Step 6: Evaluate Performance +***************************** + +.. code-block:: python + + # Calculate detection statistics + tolerance = len(preamble) + + matched_detections = [] + false_alarms = [] + + for det in detected_starts: + # Check if detection matches any true packet + matched = False + for true_start in true_starts: + if abs(det - true_start) <= tolerance: + matched_detections.append(det) + matched = True + break + if not matched: + false_alarms.append(det) + + missed_packets = len(true_starts) - len(matched_detections) + + print(f"\nPerformance Metrics:") + print(f" Correct detections: {len(matched_detections)}/{len(true_starts)}") + print(f" Missed packets: {missed_packets}") + print(f" False alarms: {len(false_alarms)}") + + # Calculate timing errors + timing_errors = [] + for det in matched_detections: + errors = [abs(det - ts) for ts in true_starts] + timing_errors.append(min(errors)) + + if len(timing_errors) > 0: + print(f" Timing error (avg): {np.mean(timing_errors):.1f} samples") + print(f" Timing error (max): {np.max(timing_errors):.1f} samples") + +Step 7: Visualize Results +************************** + +.. code-block:: python + + # Process one buffer for detailed visualization + buffer_start = max(0, true_starts[0] - 5000) + buffer_end = min(len(signal), true_starts[0] + 20000) + viz_buffer = signal[buffer_start:buffer_end] + + detections_viz, corr_viz, thresh_viz = detect_packets( + viz_buffer, preamble, cfar_guard, cfar_train, pfa_target + ) + + # Convert to global indices for plotting + detections_viz_global = [d + buffer_start for d in detections_viz] + + # Create visualization + fig, axes = plt.subplots(3, 1, figsize=(14, 10)) + time_axis = (np.arange(len(viz_buffer)) + buffer_start) / sample_rate * 1000 # ms + + # Subplot 1: Received signal power + axes[0].plot(time_axis, np.abs(viz_buffer)**2, 'gray', alpha=0.6, linewidth=0.5) + axes[0].set_ylabel('Power') + axes[0].set_title('Received IQ Signal Power') + axes[0].grid(True, alpha=0.3) + + # Mark true packet locations + for ts in true_starts: + if buffer_start <= ts <= buffer_end: + t_ms = ts / sample_rate * 1000 + axes[0].axvline(t_ms, color='green', linestyle='--', alpha=0.7, + label='True Packet' if ts == true_starts[0] else '') + axes[0].legend() + + # Subplot 2: Correlation output + axes[1].plot(time_axis, corr_viz, 'blue', linewidth=1, label='Correlation') + axes[1].plot(time_axis, thresh_viz, 'red', linestyle='--', linewidth=1.5, + label='CFAR Threshold') + axes[1].set_ylabel('Correlation Power') + axes[1].set_title('Preamble Correlation with Adaptive CFAR Threshold') + axes[1].grid(True, alpha=0.3) + axes[1].legend() + + # Subplot 3: Detections + detection_mask = np.zeros(len(viz_buffer)) + for det in detections_viz: + detection_mask[det] = corr_viz[det] + + axes[2].plot(time_axis, corr_viz, 'blue', alpha=0.4, linewidth=0.8) + axes[2].scatter(time_axis[detection_mask > 0], detection_mask[detection_mask > 0], + color='lime', edgecolors='black', s=100, zorder=5, + label='Detected Packets') + axes[2].set_xlabel('Time (ms)') + axes[2].set_ylabel('Correlation Power') + axes[2].set_title('Detected Packet Locations') + axes[2].grid(True, alpha=0.3) + axes[2].legend() + + plt.tight_layout() + plt.savefig('../_images/detection_realtime.svg', bbox_inches='tight') + plt.show() + +The visualization should show: + +1. **Top plot**: Raw signal power with true packet locations marked +2. **Middle plot**: Correlation output with adaptive CFAR threshold tracking the noise floor +3. **Bottom plot**: Detected packets highlighted as peaks above threshold + +.. image:: ../_images/detection_realtime.svg + :align: center + :target: ../_images/detection_realtime.svg + :alt: Real-time packet detection results + +Practical Considerations and Tuning +#################################### + +Buffer Size Trade-offs +*********************** + +**Larger buffers (e.g., 1M samples):** + +- ✅ Better CFAR noise estimation (more training cells) +- ✅ Lower computational overhead (fewer processing calls) +- ❌ Higher latency (must wait for buffer to fill) +- ❌ More memory required + +**Smaller buffers (e.g., 10k samples):** + +- ✅ Lower latency (faster response) +- ✅ Less memory usage +- ❌ CFAR performance degrades (fewer training cells) +- ❌ Higher CPU usage (more frequent processing) + +**Recommendation**: Start with buffer size = 10× to 100× your preamble length. For a 63-sample preamble at 1 Msps, try 10k-100k samples. + +CFAR Parameter Tuning +********************** + +The three CFAR parameters control detector behavior: + +**num_guard** (guard cells): + +- Purpose: Prevents signal leakage into noise estimate +- Too small: Signal leaks into training region → raised threshold → missed detections +- Too large: Fewer training cells → poor noise estimate +- **Rule of thumb**: Set to ~0.5 to 1.0× preamble length + +**num_train** (training cells): + +- Purpose: Estimates local noise floor +- Too small: Noisy threshold → false alarms or missed detections +- Too large: Threshold doesn't adapt quickly enough to noise changes +- **Rule of thumb**: Set to ~3 to 5× preamble length + +**pfa** (probability of false alarm): + +- Purpose: Controls detection sensitivity +- Too high (e.g., 1e-2): Many false alarms +- Too low (e.g., 1e-10): Misses weak packets +- **Rule of thumb**: Start with 1e-5 for per-lag PFA, then adjust based on system-level false alarm rate + +Remember the relationship between per-lag and system-level false alarm rates from earlier in the chapter! + +Handling Real SDR Hardware +*************************** + +When connecting to actual SDR hardware (RTL-SDR, PlutoSDR, USRP, etc.), you'll need to: + +1. **Replace simulation with SDR API**: + + .. code-block:: python + + # Example with RTL-SDR + from rtlsdr import RtlSdr + + sdr = RtlSdr() + sdr.sample_rate = 1e6 + sdr.center_freq = 915e6 # ISM band + sdr.gain = 'auto' + + # Read buffer + samples = sdr.read_samples(buffer_size) + +2. **Handle DC offset and IQ imbalance**: Real hardware often has DC spikes and phase imbalance. Pre-process with: + + .. code-block:: python + + # Remove DC offset + samples = samples - np.mean(samples) + + # Optional: High-pass filter to remove residual DC + from scipy.signal import butter, lfilter + b, a = butter(3, 0.01, btype='high') + samples = lfilter(b, a, samples) + +3. **Frequency offset correction**: If your modem's carrier frequency is slightly offset from your SDR's LO, the preamble correlation will be reduced. Consider adding coarse frequency offset estimation or using frequency-resilient correlation (covered earlier in this chapter). + +4. **Sample rate mismatch**: Ensure your SDR sample rate exactly matches the expected symbol rate of your packets. Even small mismatches accumulate over long preambles. + +Extending to Unknown Preambles +******************************* + +What if you don't know the exact preamble? Some options: + +- **Energy detection**: Use the simple ``np.abs()`` and ``np.diff()`` approach suggested earlier, but with CFAR thresholding +- **Blind synchronization**: Exploit statistical properties (cyclostationary features) covered in other chapters +- **Multi-hypothesis detection**: Run multiple correlators in parallel, one for each candidate preamble +- **Machine learning**: Train a neural network to recognize packet boundaries (advanced topic) \ No newline at end of file diff --git a/figure-generating-scripts/detection_realtime.py b/figure-generating-scripts/detection_realtime.py new file mode 100644 index 00000000..2b740301 --- /dev/null +++ b/figure-generating-scripts/detection_realtime.py @@ -0,0 +1,154 @@ +""" +Generate visualization for real-time packet detection section. +""" +import numpy as np +import matplotlib.pyplot as plt +from scipy.signal import correlate + +def ca_cfar_1d(signal, num_train, num_guard, pfa): + """Cell-Averaging CFAR detector.""" + n = len(signal) + threshold = np.zeros(n) + alpha = num_train * (pfa**(-1/num_train) - 1) + + for i in range(n): + train_start_left = max(0, i - num_guard - num_train) + train_end_left = max(0, i - num_guard) + train_start_right = min(n, i + num_guard + 1) + train_end_right = min(n, i + num_guard + num_train + 1) + + train_cells = np.concatenate([ + signal[train_start_left:train_end_left], + signal[train_start_right:train_end_right] + ]) + + if len(train_cells) > 0: + threshold[i] = alpha * np.mean(train_cells) + + return threshold + +def detect_packets(buffer, preamble, cfar_guard, cfar_train, pfa): + """Detect packets in IQ buffer.""" + corr = correlate(buffer, preamble, mode='same') + corr_power = np.abs(corr)**2 + threshold = ca_cfar_1d(corr_power, cfar_train, cfar_guard, pfa) + + detections_raw = np.where(corr_power > threshold)[0] + half_preamble = len(preamble) // 2 + detections_raw = detections_raw - half_preamble + detections_raw = detections_raw[ + (detections_raw > half_preamble) & + (detections_raw < len(buffer) - half_preamble) + ] + + detections = [] + if len(detections_raw) > 0: + detections.append(detections_raw[0]) + for det in detections_raw[1:]: + if det - detections[-1] > len(preamble): + detections.append(det) + + return detections, corr_power, threshold + +def generate_packet_stream(preamble, packet_length, num_packets, sample_rate, snr_db): + """Generate simulated IQ stream with packets.""" + signal_power = 1.0 + noise_power = signal_power / (10**(snr_db/10)) + noise_std = np.sqrt(noise_power / 2) + + qpsk_map = np.array([1+1j, -1+1j, -1-1j, 1-1j]) / np.sqrt(2) + avg_gap = int(sample_rate / 1) # 1 packet per second + + signal = [] + true_starts = [] + + for i in range(num_packets): + if i == 0: + gap_length = np.random.randint(avg_gap//2, avg_gap) + else: + gap_length = np.random.randint(int(avg_gap*0.8), int(avg_gap*1.2)) + + noise = noise_std * (np.random.randn(gap_length) + 1j*np.random.randn(gap_length)) + signal.extend(noise) + + true_starts.append(len(signal)) + + data_length = packet_length - len(preamble) + data = qpsk_map[np.random.randint(0, 4, data_length)] + packet = np.concatenate([preamble, data]) + packet_noisy = packet + noise_std * (np.random.randn(len(packet)) + + 1j*np.random.randn(len(packet))) + signal.extend(packet_noisy) + + gap_length = np.random.randint(avg_gap//2, avg_gap) + noise = noise_std * (np.random.randn(gap_length) + 1j*np.random.randn(gap_length)) + signal.extend(noise) + + return np.array(signal), true_starts + +# Generate test signal +np.random.seed(42) +N_zc, u = 63, 5 +t = np.arange(N_zc) +preamble = np.exp(-1j * np.pi * u * t * (t + 1) / N_zc) + +sample_rate = 1e6 +packet_length = 500 +snr_db = -5 + +signal, true_starts = generate_packet_stream( + preamble, packet_length, num_packets=5, + sample_rate=sample_rate, snr_db=snr_db +) + +# Process one buffer for visualization +buffer_start = max(0, true_starts[0] - 5000) +buffer_end = min(len(signal), true_starts[2] + 10000) +viz_buffer = signal[buffer_start:buffer_end] + +detections_viz, corr_viz, thresh_viz = detect_packets( + viz_buffer, preamble, cfar_guard=10, cfar_train=50, pfa=1e-5 +) + +# Create visualization +fig, axes = plt.subplots(3, 1, figsize=(14, 10)) +time_axis = (np.arange(len(viz_buffer)) + buffer_start) / sample_rate * 1000 + +# Subplot 1: Received signal power +axes[0].plot(time_axis, np.abs(viz_buffer)**2, 'gray', alpha=0.6, linewidth=0.5) +axes[0].set_ylabel('Power', fontsize=11) +axes[0].set_title('Received IQ Signal Power', fontsize=12, fontweight='bold') +axes[0].grid(True, alpha=0.3) +for i, ts in enumerate(true_starts): + if buffer_start <= ts <= buffer_end: + t_ms = ts / sample_rate * 1000 + axes[0].axvline(t_ms, color='green', linestyle='--', alpha=0.7, linewidth=1.5, + label='True Packet' if i == 0 else '') +axes[0].legend(fontsize=10) + +# Subplot 2: Correlation output +axes[1].plot(time_axis, corr_viz, 'blue', linewidth=1, label='Correlation') +axes[1].plot(time_axis, thresh_viz, 'red', linestyle='--', linewidth=1.5, label='CFAR Threshold') +axes[1].set_ylabel('Correlation Power', fontsize=11) +axes[1].set_title('Preamble Correlation with Adaptive CFAR Threshold', fontsize=12, fontweight='bold') +axes[1].grid(True, alpha=0.3) +axes[1].legend(fontsize=10) + +# Subplot 3: Detections +detection_mask = np.zeros(len(viz_buffer)) +for det in detections_viz: + detection_mask[det] = corr_viz[det] + +axes[2].plot(time_axis, corr_viz, 'blue', alpha=0.4, linewidth=0.8) +axes[2].scatter(time_axis[detection_mask > 0], detection_mask[detection_mask > 0], + color='lime', edgecolors='black', s=100, zorder=5, label='Detected Packets') +axes[2].set_xlabel('Time (ms)', fontsize=11) +axes[2].set_ylabel('Correlation Power', fontsize=11) +axes[2].set_title('Detected Packet Locations', fontsize=12, fontweight='bold') +axes[2].grid(True, alpha=0.3) +axes[2].legend(fontsize=10) + +plt.tight_layout() +plt.savefig('../_images/detection_realtime.svg', bbox_inches='tight', dpi=150) +print("Figure saved to ../_images/detection_realtime.svg") +plt.show() \ No newline at end of file From fd67f4b6b7f1f445eacd6f402340bb8d6c545640 Mon Sep 17 00:00:00 2001 From: vishwaksen-1 Date: Thu, 5 Feb 2026 22:48:06 +0530 Subject: [PATCH 2/3] Add full E2E QPSK simulation to Sync chapter --- content/sync.rst | 276 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 267 insertions(+), 9 deletions(-) diff --git a/content/sync.rst b/content/sync.rst index 52c92d54..30803934 100644 --- a/content/sync.rst +++ b/content/sync.rst @@ -484,13 +484,271 @@ You can see it's 11 (length of the sequence) in the center, and -1 or 0 for all Another sequence with great autocorrelation properties is Zadoff-Chu sequences, which are used in LTE. They have the benefit of being in sets; you can have multiple different sequences that all have good autocorrelation properties, but they won't trigger each other (i.e., also good cross-correlation properties, when you cross-correlate different sequences in the set). Thanks to that feature, different cell towers will be assigned different sequences so that a phone can not only find the start of the frame but also know which tower it is receiving from. +*********************************************** +Full End-to-End QPSK Communication Example +*********************************************** + +Now that we've learned about time and frequency synchronization, let's put it all together in a complete end-to-end simulation. This example demonstrates a full QPSK communication system that: + +1. Takes an ASCII text message as input +2. Modulates it using QPSK +3. Applies pulse shaping with a raised-cosine filter +4. Simulates a realistic wireless channel (frequency offset, timing delay, and noise) +5. Performs time synchronization using the Mueller & Muller algorithm +6. Performs frequency synchronization using a Costas Loop +7. Demodulates the QPSK symbols +8. Resolves phase ambiguity +9. Decodes the message and calculates Bit Error Rate (BER) - - - - - - - - - +.. code-block:: python + + import numpy as np + import matplotlib.pyplot as plt + from scipy import signal + + # Step 1: Generate Input Data (ASCII Message) + message = "Hello SDR!" + print(f"Original Message: {message}") + + # Convert ASCII to bits + bits = [] + for char in message: + byte = format(ord(char), '08b') # Convert to 8-bit binary + bits.extend([int(b) for b in byte]) + bits = np.array(bits) + print(f"Total bits: {len(bits)}") + + # Step 2: QPSK Modulation + # Map bits to QPSK symbols: 00->-1-1j, 01->-1+1j, 10->1-1j, 11->1+1j + num_symbols = len(bits) // 2 + symbols = np.zeros(num_symbols, dtype=complex) + + for i in range(num_symbols): + bit_pair = (bits[2*i], bits[2*i+1]) + if bit_pair == (0, 0): + symbols[i] = -1-1j + elif bit_pair == (0, 1): + symbols[i] = -1+1j + elif bit_pair == (1, 0): + symbols[i] = 1-1j + else: # (1, 1) + symbols[i] = 1+1j + + # Normalize to unit power + symbols = symbols / np.sqrt(2) + + # Step 3: Pulse Shaping (Raised Cosine Filter) + sps = 8 # samples per symbol + num_taps = 101 + beta = 0.35 + Ts = sps + t = np.arange(-51, 52) + h = np.sinc(t/Ts) * np.cos(np.pi*beta*t/Ts) / (1 - (2*beta*t/Ts)**2) + + # Upsample symbols + upsampled = np.zeros(len(symbols) * sps, dtype=complex) + upsampled[::sps] = symbols + + # Apply pulse shaping + tx_samples = np.convolve(upsampled, h, mode='same') + + # Step 4: Channel Simulation + # Add frequency offset + fs = 1e6 + fo = 800 # 800 Hz frequency offset + Ts_sample = 1/fs + t_vec = np.arange(len(tx_samples)) * Ts_sample + channel_samples = tx_samples * np.exp(1j*2*np.pi*fo*t_vec) + + # Add fractional delay + delay = 0.4 + N_delay = 21 + n = np.arange(-N_delay//2, N_delay//2) + h_delay = np.sinc(n - delay) + h_delay *= np.hamming(N_delay) + h_delay /= np.sum(h_delay) + channel_samples = np.convolve(channel_samples, h_delay, mode='same') + + # Add AWGN noise + SNR_dB = 15 + signal_power = np.mean(np.abs(channel_samples)**2) + noise_power = signal_power / (10**(SNR_dB/10)) + noise = np.sqrt(noise_power/2) * (np.random.randn(len(channel_samples)) + + 1j*np.random.randn(len(channel_samples))) + rx_samples = channel_samples + noise + + # Step 5: Time Synchronization (Mueller & Muller) + # Interpolate for fractional timing recovery + samples_interpolated = signal.resample_poly(rx_samples, 16, 1) + + mu = 0 + out = np.zeros(len(rx_samples) + 10, dtype=complex) + out_rail = np.zeros(len(rx_samples) + 10, dtype=complex) + i_in = 0 + i_out = 2 + + while i_out < len(rx_samples) and i_in*16+16 < len(samples_interpolated): + out[i_out] = samples_interpolated[i_in*16 + int(mu*16)] + out_rail[i_out] = (np.real(out[i_out])>0)*2-1 + 1j*((np.imag(out[i_out])>0)*2-1) + x = (out_rail[i_out] - out_rail[i_out-2]) * np.conj(out[i_out-1]) + y = (out[i_out] - out[i_out-2]) * np.conj(out_rail[i_out-1]) + mm_val = np.real(y - x) + mu += sps + 0.01*mm_val # Reduced gain for stability + i_in += int(np.floor(mu)) + mu = mu - np.floor(mu) + i_out += 1 + + out = out[2:i_out] + time_synced = out + + # Step 6: Frequency Synchronization (Costas Loop) + N = len(time_synced) + phase = 0 + freq = 0 + alpha = 0.01 # Reduced proportional gain to prevent oscillations + beta = 0.001 # Reduced integral gain + freq_synced = np.zeros(N, dtype=complex) + + for i in range(N): + freq_synced[i] = time_synced[i] * np.exp(-1j*phase) + error = np.real(freq_synced[i]) * np.imag(freq_synced[i]) + + freq += beta * error + phase += freq + alpha * error + + while phase >= 2*np.pi: + phase -= 2*np.pi + while phase < 0: + phase += 2*np.pi + + # Step 7: QPSK Demodulation + # Skip first 35 symbols (allow sync algorithms to fully converge) + skip = min(35, len(freq_synced) // 2) # At least 35 symbols or half the data + demod_symbols = freq_synced[skip:] + + # Try all 4 phase rotations and pick the one with lowest error + best_ber = 1.0 + best_rotation = 0 + best_bits = None + + for phase_rotation in range(4): + # Rotate by 0, π/2, π, 3π/2 + rotation = np.exp(1j * np.pi / 2 * phase_rotation) + rotated_symbols = demod_symbols * rotation + + # Decode QPSK symbols back to bits + test_bits = [] + for sym in rotated_symbols: + I = np.real(sym) + Q = np.imag(sym) + + if I >= 0 and Q >= 0: + test_bits.extend([1, 1]) + elif I < 0 and Q >= 0: + test_bits.extend([0, 1]) + elif I >= 0 and Q < 0: + test_bits.extend([1, 0]) + else: + test_bits.extend([0, 0]) + + test_bits = np.array(test_bits) + if len(test_bits) > len(bits): + test_bits = test_bits[:len(bits)] + elif len(test_bits) < len(bits): + test_bits = np.pad(test_bits, (0, len(bits) - len(test_bits))) + + # Calculate BER for this rotation + ber_test = np.sum(bits != test_bits) / len(bits) + if ber_test < best_ber: + best_ber = ber_test + best_rotation = phase_rotation + best_bits = test_bits + + received_bits = best_bits + print(f"Best phase rotation: {best_rotation * 90}°") + + # Step 8: Decode ASCII and Calculate BER + received_bits = np.array(received_bits) + + # Trim to match original length + if len(received_bits) > len(bits): + received_bits = received_bits[:len(bits)] + elif len(received_bits) < len(bits): + # Pad with zeros if we have fewer bits + received_bits = np.pad(received_bits, (0, len(bits) - len(received_bits))) + + # Calculate BER + bit_errors = np.sum(bits != received_bits) + ber = bit_errors / len(bits) + + # Decode received message + received_message = "" + for i in range(len(received_bits) // 8): + byte_bits = received_bits[i*8:(i+1)*8] + byte_str = ''.join([str(b) for b in byte_bits]) + received_message += chr(int(byte_str, 2)) + + # Step 9: Display Results + print("\n" + "="*50) + print("E2E QPSK Communication System Results") + print("="*50) + print(f"Transmitted Message: {message}") + print(f"Received Message: {received_message}") + print(f"SNR: {SNR_dB} dB") + print(f"Frequency Offset: {fo} Hz") + print(f"Bit Error Rate: {ber:.6f} ({bit_errors}/{len(bits)} bit errors)") + print("="*50) + + # Plot constellation before and after sync + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) + ax1.plot(np.real(rx_samples[::sps]), np.imag(rx_samples[::sps]), '.') + ax1.set_title('Before Synchronization') + ax1.set_xlabel('I') + ax1.set_ylabel('Q') + ax1.grid(True) + ax1.axis('equal') + + ax2.plot(np.real(freq_synced[skip:]), np.imag(freq_synced[skip:]), '.') + ax2.set_title('After Synchronization') + ax2.set_xlabel('I') + ax2.set_ylabel('Q') + ax2.grid(True) + ax2.axis('equal') + + plt.tight_layout() + plt.savefig('sync_e2e_qpsk_constellation.png', dpi=150, bbox_inches='tight') + plt.show() + +When you run this simulation, you should see: + +- The original transmitted message printed to the console +- The received message (which should closely match the original, depending on SNR and channel conditions) +- The Bit Error Rate (BER), showing the ratio of incorrectly received bits +- Two constellation diagrams side-by-side: + + * **Left plot**: The received signal before synchronization, showing the effects of frequency offset, timing errors, and noise. The constellation points will appear rotated and spread out. + * **Right plot**: The signal after both time and frequency synchronization. The QPSK constellation should show four distinct clusters near the ideal symbol locations (±1±j)/√2. + +**Understanding the Results** + +This example demonstrates several important concepts: + +- **Phase Ambiguity**: QPSK (and most modulations) have inherent phase ambiguity. The Costas Loop can lock to any of four possible phase offsets (0°, 90°, 180°, 270°). The code resolves this by trying all four rotations and selecting the one with the lowest BER. + +- **Convergence Time**: The synchronization algorithms need time to converge. That's why we skip the first 35 symbols before demodulation. In a real system, you would use a preamble or training sequence for synchronization. + +- **SNR Impact**: Try adjusting the :code:`SNR_dB` parameter. Lower SNR values will increase the BER, while higher values should result in near-perfect reception. + +- **Frequency Offset**: The :code:`fo` parameter simulates oscillator drift or Doppler shift. Larger offsets make synchronization more challenging. + +**Exercises** + +Try modifying the code to explore different scenarios: + +1. Change the SNR to see how it affects BER (try values from 5 dB to 25 dB) +2. Increase the frequency offset to 2000 Hz or 5000 Hz and observe the impact +3. Modify the message to be longer and see if the BER changes +4. Replace QPSK with BPSK by changing the symbol mapping (you'll need to adjust the demodulator accordingly) +5. Add a plot showing BER vs SNR by running the simulation multiple times + +This simulation ties together all the concepts we've covered in this chapter: pulse shaping, channel effects, timing recovery, and frequency synchronization. It demonstrates that even with impairments like frequency offset, timing errors, and noise, we can successfully recover digital data using the synchronization techniques we've learned. \ No newline at end of file From 87b9b6121ec0bee52641a7fb0169353e3535edde Mon Sep 17 00:00:00 2001 From: vishwaksen-1 Date: Tue, 17 Feb 2026 01:35:09 +0530 Subject: [PATCH 3/3] Add Random Variables chapter and improve Detection flowchart --- _images/gaussian_IQ.png | Bin 0 -> 63027 bytes _images/gaussian_histogram.png | Bin 0 -> 25668 bytes _images/gaussian_transformed.png | Bin 0 -> 57544 bytes conf.py | 1 + content/detection.rst | 146 +------ content/random_variables.rst | 362 ++++++++++++++++++ figure-generating-scripts/random_variables.py | 69 ++++ 7 files changed, 453 insertions(+), 125 deletions(-) create mode 100644 _images/gaussian_IQ.png create mode 100644 _images/gaussian_histogram.png create mode 100644 _images/gaussian_transformed.png create mode 100644 content/random_variables.rst create mode 100644 figure-generating-scripts/random_variables.py diff --git a/_images/gaussian_IQ.png b/_images/gaussian_IQ.png new file mode 100644 index 0000000000000000000000000000000000000000..74147280a572d6f9e82980fbf1b8c729b7f34716 GIT binary patch literal 63027 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 literal 0 HcmV?d00001 diff --git a/_images/gaussian_transformed.png b/_images/gaussian_transformed.png new file mode 100644 index 0000000000000000000000000000000000000000..3b56ceb4bc4036beecc9df8c893a3a6054d8ecd7 GIT binary patch literal 57544 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 MHz sample rate)") + B("Buffer Accumulation
(100k samples = 0.1 sec)") + C("Cross-Correlation with Known Preamble") + D("CFAR Threshold Computation") + E("Peak Detection
(correlation > threshold)") + F("Packet Extraction & Validation") -The key insight is that we don't need to process every single sample individually. Instead, we: -- Accumulate samples in **buffers** (chunks of, say, 100,000 samples) -- Run our detector on each buffer -- Extract detected packets -- Maintain state across buffer boundaries to avoid missing packets that span buffers + A --> B --> C --> D --> E --> F -Implementation Strategy -####################### -Our detector will follow this workflow: +To avoid missing packets that straddle buffer boundaries, we use an **overlap-save** approach, where each buffer includes the last ``N_preamble`` samples from the previous buffer. This ensures any packet starting near the end of buffer ``i`` will be fully contained in buffer ``i+1``. This requires a small additional computational overhead but we don't want to miss packets just because they straddle buffer boundaries. -.. code-block:: text - - ┌─────────────────────────────────────────────────────────────┐ - │ Continuous IQ Stream from SDR (e.g., 1 Msps) │ - └────────────────────┬────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────────────────────────────┐ - │ Buffer Accumulation (e.g., 100k samples = 0.1 sec) │ - └────────────────────┬────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────────────────────────────┐ - │ Cross-Correlation with Known Preamble │ - │ → Produces correlation vs. sample index │ - └────────────────────┬────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────────────────────────────┐ - │ CFAR Threshold Computation │ - │ → Adaptive threshold that tracks noise floor │ - └────────────────────┬────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────────────────────────────┐ - │ Peak Detection (correlation > threshold) │ - │ → List of candidate packet start indices │ - └────────────────────┬────────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────────────────────────────┐ - │ Packet Extraction & Validation │ - │ → Extract samples, pass to demodulator │ - └─────────────────────────────────────────────────────────────┘ - -**Buffer Overlap Strategy** - -To avoid missing packets that straddle buffer boundaries, we use an **overlap-save** approach: - -- Each buffer includes the last ``N_preamble`` samples from the previous buffer -- This ensures any packet starting near the end of buffer ``i`` will be fully contained in buffer ``i+1`` -- Trade-off: Small computational overhead vs. guaranteed detection - -Python Implementation -##################### - -Let's build a complete packet detector step by step. We'll use a Zadoff-Chu preamble (as introduced earlier) and implement adaptive CFAR detection. +Let's build a complete packet detector in Python one step at a time. We'll use a Zadoff-Chu preamble as introduced earlier, but with a shorter length, and implement an adaptive CFAR detector. Step 1: Define the Preamble and Parameters ******************************************* @@ -924,49 +866,3 @@ The three CFAR parameters control detector behavior: - **Rule of thumb**: Start with 1e-5 for per-lag PFA, then adjust based on system-level false alarm rate Remember the relationship between per-lag and system-level false alarm rates from earlier in the chapter! - -Handling Real SDR Hardware -*************************** - -When connecting to actual SDR hardware (RTL-SDR, PlutoSDR, USRP, etc.), you'll need to: - -1. **Replace simulation with SDR API**: - - .. code-block:: python - - # Example with RTL-SDR - from rtlsdr import RtlSdr - - sdr = RtlSdr() - sdr.sample_rate = 1e6 - sdr.center_freq = 915e6 # ISM band - sdr.gain = 'auto' - - # Read buffer - samples = sdr.read_samples(buffer_size) - -2. **Handle DC offset and IQ imbalance**: Real hardware often has DC spikes and phase imbalance. Pre-process with: - - .. code-block:: python - - # Remove DC offset - samples = samples - np.mean(samples) - - # Optional: High-pass filter to remove residual DC - from scipy.signal import butter, lfilter - b, a = butter(3, 0.01, btype='high') - samples = lfilter(b, a, samples) - -3. **Frequency offset correction**: If your modem's carrier frequency is slightly offset from your SDR's LO, the preamble correlation will be reduced. Consider adding coarse frequency offset estimation or using frequency-resilient correlation (covered earlier in this chapter). - -4. **Sample rate mismatch**: Ensure your SDR sample rate exactly matches the expected symbol rate of your packets. Even small mismatches accumulate over long preambles. - -Extending to Unknown Preambles -******************************* - -What if you don't know the exact preamble? Some options: - -- **Energy detection**: Use the simple ``np.abs()`` and ``np.diff()`` approach suggested earlier, but with CFAR thresholding -- **Blind synchronization**: Exploit statistical properties (cyclostationary features) covered in other chapters -- **Multi-hypothesis detection**: Run multiple correlators in parallel, one for each candidate preamble -- **Machine learning**: Train a neural network to recognize packet boundaries (advanced topic) \ No newline at end of file diff --git a/content/random_variables.rst b/content/random_variables.rst new file mode 100644 index 00000000..0940ef23 --- /dev/null +++ b/content/random_variables.rst @@ -0,0 +1,362 @@ +.. _random-variables-chapter: + +###################################### +Random Variables and Random Processes +###################################### + +In this chapter we introduce the fundamental concepts of random variables and random processes, which are essential for understanding noise, channel effects, and many signal processing techniques in wireless communications. We'll cover probability distributions, expectation, variance, and how random processes evolve over time. These concepts form the mathematical foundation for analyzing noise in the :ref:`noise-chapter` chapter and many other topics throughout SDR and DSP. + +*************************** +What is a Random Variable? +*************************** + +A **random variable** is a mathematical concept that maps outcomes of a random experiment to numerical values. Unlike the deterministic signals we've worked with so far, random variables represent quantities whose values are uncertain until they are observed or measured. + +Think of rolling a six-sided die. Before you roll it, you don't know what number will appear. We can define a random variable :math:`X` that represents the outcome of the roll. The value of :math:`X` is one of {1, 2, 3, 4, 5, 6}, but we don't know which one until we actually roll the die. + +In the context of wireless communications and SDR, random variables are everywhere: + +* The thermal noise in a receiver is modeled as a random variable at each instant in time +* The amplitude of a received signal affected by fading is random +* The phase offset introduced by a channel can be modeled as random +* Even the data bits we transmit can be treated as random variables (if we don't know them ahead of time) + +**Single Sample vs. Many Samples** + +This is a crucial distinction that often causes confusion: + +* A **single realization** or **single sample** of a random variable is just one number—one outcome of the random experiment +* To characterize a random variable (find its average, spread, etc.), we need **many realizations**—many outcomes + +For example, if you call ``np.random.randn()`` in Python without any arguments, it returns a single random number drawn from a Gaussian distribution. That single number tells you almost nothing about the distribution itself. But if you call ``np.random.randn(10000)`` and generate 10,000 samples, you can now estimate properties of the distribution like its mean and variance. + +.. code-block:: python + + import numpy as np + + # Single sample - just one number + x_single = np.random.randn() + print(x_single) # might be 0.534, -1.23, or any other value + + # Many samples - now we can characterize the distribution + x_many = np.random.randn(10000) + print(np.mean(x_many)) # will be close to 0 + print(np.var(x_many)) # will be close to 1 + +Joint Distributions +#################### + +So far we've focused on single random variables. When dealing with two or more random variables simultaneously, we use a **joint distribution**. + +For continuous variables :math:`X` and :math:`Y`, this is described by the **joint PDF**: + +.. math:: + f_{X,Y}(x,y) + +The joint PDF tells us how likely it is for :math:`X` to take value :math:`x` *and* :math:`Y` to take value :math:`y` at the same time. + +From the joint PDF, we can compute: + +* Marginal PDFs (e.g., :math:`f_X(x)` or :math:`f_Y(y)`) +* Expectations such as :math:`E[XY]` +* Covariance and correlation +* Probabilities involving both variables + +For example, the marginal PDF of :math:`X` is obtained by integrating out :math:`Y`: + +.. math:: + f_X(x) = \int_{-\infty}^{\infty} f_{X,Y}(x,y)\,dy + +Joint distributions are the mathematical foundation for understanding dependence, correlation, and independence between random variables. + +*************************** +Probability Distributions +*************************** + +A **probability distribution** describes how likely different values of a random variable are. For a continuous random variable, we use a **probability density function (PDF)**, denoted :math:`f_X(x)`. The PDF tells us the relative likelihood of the random variable taking on different values. + +The most important distribution in SDR and communications is the **Gaussian (Normal) distribution**. A Gaussian random variable :math:`X` with mean :math:`\mu` and variance :math:`\sigma^2` has the PDF: + +.. math:: + f_X(x) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(x-\mu)^2}{2\sigma^2}} + +This is the famous "bell curve" you've likely seen before. The distribution is completely characterized by two parameters: + +* **Mean** :math:`\mu`: the center of the distribution +* **Variance** :math:`\sigma^2`: how spread out the distribution is (standard deviation :math:`\sigma` is the square root of variance) + +In Python, ``np.random.randn()`` generates samples from a **standard Gaussian** distribution with :math:`\mu = 0` and :math:`\sigma^2 = 1`. We can visualize this: + +.. code-block:: python + + import numpy as np + import matplotlib.pyplot as plt + + # Generate 10,000 samples from standard Gaussian + x = np.random.randn(10000) + + # Create histogram to visualize the distribution + plt.hist(x, bins=50, density=True, alpha=0.7, edgecolor='black') + plt.xlabel('Value') + plt.ylabel('Probability Density') + plt.title('Gaussian Distribution (μ=0, σ²=1)') + plt.grid(True) + plt.show() + +.. image:: ../_images/gaussian_histogram.png + :scale: 80% + :align: center + :alt: Histogram of Gaussian distributed samples + +Expectation (Mean) +####################### + +The **expectation** or **expected value** of a random variable, denoted :math:`E[X]` or :math:`\mu`, represents its average value over many realizations. For a continuous random variable with PDF :math:`f_X(x)`, the expectation is: + +.. math:: + E[X] = \int_{-\infty}^{\infty} x \cdot f_X(x) \, dx + +In practice, when we have :math:`N` samples :math:`x_1, x_2, \ldots, x_N` drawn from the distribution, we estimate the expectation using the **sample mean**: + +.. math:: + \hat{\mu} = \frac{1}{N} \sum_{n=1}^{N} x_n + +The expectation is a **linear operator**, which means: + +* :math:`E[aX + b] = aE[X] + b` for constants :math:`a` and :math:`b` +* :math:`E[X + Y] = E[X] + E[Y]` for any two random variables + +This linearity is extremely useful in signal processing! + +Variance and Standard Deviation +################################# + +The **variance** of a random variable, denoted :math:`\text{Var}(X)` or :math:`\sigma^2`, measures how spread out its values are around the mean. It's defined as the expected value of the squared deviation from the mean: + +.. math:: + \text{Var}(X) = E[(X - \mu)^2] = E[X^2] - (E[X])^2 + +When we have :math:`N` samples, we estimate variance using: + +.. math:: + \hat{\sigma}^2 = \frac{1}{N} \sum_{n=1}^{N} (x_n - \hat{\mu})^2 + +The **standard deviation** :math:`\sigma` is simply the square root of variance: :math:`\sigma = \sqrt{\sigma^2}`. + +*Notice the* ^ (hat) *in the above equation at* :math:`\sigma` *and that for sample mean. The hat symbolizes we're estimating the mean/variance. It's not always exactly equal to the true mean/variance, but it gets closer to the true value as we increase the number of samples* + +**Key Property:** If :math:`X` is a random variable with variance :math:`\sigma^2`, then: + +* Scaling: :math:`\text{Var}(aX) = a^2 \text{Var}(X)` +* Shifting: :math:`\text{Var}(X + b) = \text{Var}(X)` (adding a constant doesn't change the spread) + +And consequently for standard deviation :math:`\sigma`: + +* Scaling: :math:`\sigma(aX) = a\sigma(X)` +* Shifting: :math:`\sigma(X+b) = \sigma(X)` + +.. image:: ../_images/gaussian_transformed.png + :scale: 80% + :align: center + :alt: Scaling and shifting the Gaussian Distribution. (notice the scales on x and y axes) + +Scaling and shifting the Gaussian Distribution. (notice the scales on x and y axes) + +**Variance and Power** + +In signal processing, for a **zero-mean** signal (mean ~ 0), the variance equals the **average power**. This is why we often use the terms interchangeably: + +.. math:: + P = \text{Var}(X) = E[X^2] \quad \text{(when } E[X] = 0\text{)} + +This relationship is fundamental in analyzing noise power, signal-to-noise ratio (SNR), and link budgets. + +.. code-block:: python + + noise_power = 2.0 + n = np.random.randn(N) * np.sqrt(noise_power) + print(np.var(n)) # will be approximately 2.0 + +Covariance +------------------ + +The **covariance** between two random variables :math:`X` and :math:`Y` is defined as: + +.. math:: + \text{Cov}(X,Y) = E[(X - E[X])(Y - E[Y])] + +An equivalent and often more convenient form is: + +.. math:: + \text{Cov}(X,Y) = E[XY] - E[X]E[Y] + +Covariance measures how two variables vary together: + +* Positive covariance: they tend to increase and decrease together +* Negative covariance: one tends to increase when the other decreases +* Zero covariance: they are uncorrelated + +If both variables are zero-mean, this simplifies to: + +.. math:: + \text{Cov}(X,Y) = E[XY] + +Covariance has units (it is not normalized), which is why we often use the **correlation coefficient** (or simply correlation) in practice: + +.. math:: + \rho_{XY} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} + +This produces a dimensionless value between −1 and +1. + +Variance of a Sum of Variables +############################### + +In signal processing we often deal with sums of random variables, such as a signal plus noise: + +.. math:: + Z = X + Y + +The variance of this sum depends on whether :math:`X` and :math:`Y` are independent (or more generally, correlated). + +In full generality: + +.. math:: + \text{Var}(X + Y) = \text{Var}(X) + \text{Var}(Y) + 2\,\text{Cov}(X,Y) + +where :math:`\text{Cov}(X,Y)` is the **covariance** between :math:`X` and :math:`Y`. + +**Independent Case** + +If :math:`X` and :math:`Y` are independent (or simply uncorrelated), then the expression simplifies to: + +.. math:: + \text{Var}(X + Y) = \text{Var}(X) + \text{Var}(Y) + +This result is extremely important in communications. For example, if a received signal is: + +.. math:: + R = S + N + +where :math:`S` is the signal and :math:`N` is independent noise, then the total power is just the sum of signal power and noise power. + +This is why SNR calculations are so straightforward. + +*********************************** +Complex Random Variables +*********************************** + +In SDR, we work extensively with **complex-valued signals**, which means we also work with complex random variables. A complex random variable has the form: + +.. math:: + Z = X + jY + +where :math:`X` and :math:`Y` are both real-valued random variables representing the in-phase (I) and quadrature (Q) components. + +**Complex Gaussian Noise** + +The most common complex random variable in wireless communications is **complex Gaussian noise**, where both :math:`X` and :math:`Y` are independent Gaussian random variables with the same variance. + +For example, if :math:`X \sim \mathcal{N}(\alpha_1, \sigma_1^2)` and :math:`Y \sim \mathcal{N}(\alpha_2, \sigma_2^2)` are independent, then the complex random variable :math:`Z = X + jY` has: + +* Mean: :math:`E[Z] = E[X] + jE[Y] = \alpha_1 + j\alpha_2` +* Variance (Power): :math:`\text{Var}(Z) = \text{Var}(X) + \text{Var}(Y) = \sigma_1^2 + \sigma_2^2` + +.. image:: ../_images/gaussian_IQ.png + :scale: 80% + :align: center + +This is why when we create complex Gaussian noise with unit power (variance = 1), we use: + +.. code-block:: python + + N = 10000 + n = (np.random.randn(N) + 1j*np.random.randn(N)) / np.sqrt(2) + print(np.var(n)) # ~ 1 + +The division by :math:`\sqrt{2}` ensures that the total power (sum of I and Q variances) equals 1. + +.. code-block:: python + + # Without normalization: + n_raw = np.random.randn(N) + 1j*np.random.randn(N) + print(np.var(np.real(n_raw))) # ~ 1 + print(np.var(np.imag(n_raw))) # ~ 1 + print(np.var(n_raw)) # ~ 2 (total power) + + # With normalization: + n_norm = n_raw / np.sqrt(2) + print(np.var(n_norm)) # ~ 1 (unit power) + +*********************************** +Random Processes +*********************************** + +So far we've discussed random variables—random values at a single point. A **random process** (also called a **stochastic process**) is a collection of random variables indexed by time: + +.. math:: + X(t) \quad \text{or} \quad X[n] \text{ for discrete time} + +At each time :math:`t`, :math:`X(t)` is a random variable. Think of a random process as a signal that evolves randomly over time. + +Examples in wireless communications: + +* Noise at the receiver: :math:`N(t)` or :math:`N[n]` +* A signal experiencing time-varying fading: :math:`H(t)S(t)` +* Samples from an SDR: each batch is a realization of a random process + +**Stationary Processes** + +A random process is **stationary** if its statistical properties don't change over time. In particular, a **wide-sense stationary (WSS)** process has: + +* Constant mean: :math:`E[X(t)] = \mu` for all :math:`t` +* Autocorrelation that depends only on time difference: :math:`E[X(t)X(t+\tau)]` depends only on :math:`\tau`, not :math:`t` + +Many noise sources in wireless systems are approximately stationary, which simplifies analysis significantly. + +**White Noise** + +**White noise** is a random process where samples at different times are uncorrelated, and the power spectral density is constant across all frequencies. Additive White Gaussian Noise (AWGN) is both: + +* **White**: uncorrelated in time, flat power spectrum +* **Gaussian**: each sample is Gaussian distributed + +When we generate noise in Python using ``np.random.randn(N)``, each of the :math:`N` samples is an independent Gaussian random variable, creating a white noise process. + + +Independence and Correlation +############################# + +Two random variables :math:`X` and :math:`Y` are **independent** if knowing the value of one tells you nothing about the other. Mathematically, their joint PDF factors: + +.. math:: + f_{X,Y}(x,y) = f_X(x) \cdot f_Y(y) + +Independence is a strong condition. A weaker condition is **uncorrelated**, which means: + +.. math:: + E[XY] = E[X]E[Y] + +For Gaussian random variables, uncorrelated implies independent (this is a special property of Gaussians). + +In complex Gaussian noise, the I and Q components are independent: + +.. code-block:: python + + N = 10000 + I = np.random.randn(N) + Q = np.random.randn(N) + + # Check independence via correlation + correlation = np.corrcoef(I, Q)[0, 1] + print(f"Correlation between I and Q: {correlation:.4f}") # ~ 0 + + +*************************** +Further Reading +*************************** + +1. Papoulis, A., & Pillai, S. U. (2002). *Probability, Random Variables, and Stochastic Processes*. McGraw-Hill. +2. Kay, S. M. (2006). *Intuitive Probability and Random Processes using MATLAB®*. Springer. +3. https://en.wikipedia.org/wiki/Random_variable +4. https://en.wikipedia.org/wiki/Normal_distribution +5. https://en.wikipedia.org/wiki/Stochastic_process diff --git a/figure-generating-scripts/random_variables.py b/figure-generating-scripts/random_variables.py new file mode 100644 index 00000000..7f458918 --- /dev/null +++ b/figure-generating-scripts/random_variables.py @@ -0,0 +1,69 @@ +import numpy as np +import matplotlib.pyplot as plt + +# Generate 10,000 samples from standard Gaussian +x = np.random.randn(10000) + +# Create histogram to visualize the distribution +plt.hist(x, bins=50, density=True, alpha=0.7, edgecolor='black') +plt.xlabel('Value') +plt.ylabel('Probability Density') +plt.title('Gaussian Distribution (μ=0, σ²=1)') +plt.grid(True) +plt.show() + +# Simulation parameters +N = 10000 + +# Generate standard Gaussian random variables (mean=0, var=1) +x = np.random.randn(N) + +# Create different random variables by scaling and shifting +y1 = x # mean=0, var=1 +y2 = 2 * x # mean=0, var=4 +y3 = x + 3 # mean=3, var=1 +y4 = 0.5 * x - 1 # mean=-1, var=0.25 + +# Verify properties +signals = [y1, y2, y3, y4] +labels = ['y1: x', 'y2: 2x', 'y3: x+3', 'y4: 0.5x-1'] + +for i, (sig, label) in enumerate(zip(signals, labels)): + print(f"{label}") + print(f" Sample mean: {np.mean(sig):.3f}") + print(f" Sample variance: {np.var(sig):.3f}") + print() + +# Plot histograms +fig, axes = plt.subplots(2, 2, figsize=(10, 8)) +axes = axes.flatten() + +for i, (sig, label, ax) in enumerate(zip(signals, labels, axes)): + ax.hist(sig, bins=50, density=True, alpha=0.7, edgecolor='black') + ax.set_title(label) + ax.set_xlabel('Value') + ax.set_ylabel('Density') + ax.grid(True) + +plt.tight_layout() +plt.show() + +# Complex Gaussian noise demonstration +n_complex = (np.random.randn(N) + 1j*np.random.randn(N)) / np.sqrt(2) + +print("Complex Gaussian Noise (unit power):") +print(f" Real part variance: {np.var(np.real(n_complex)):.3f}") +print(f" Imag part variance: {np.var(np.imag(n_complex)):.3f}") +print(f" Total variance: {np.var(n_complex):.3f}") + +# Plot on IQ plane +plt.figure(figsize=(6, 6)) +plt.plot(np.real(n_complex[:1000]), np.imag(n_complex[:1000]), '.', alpha=0.3) +plt.xlabel('In-phase (I)') +plt.ylabel('Quadrature (Q)') +plt.title('Complex Gaussian Noise on IQ Plane') +plt.grid(True) +plt.axis('equal') +plt.xlim([-3, 3]) +plt.ylim([-3, 3]) +plt.show() \ No newline at end of file