diff --git a/scenario_simulator/core/simulation_engine.py b/scenario_simulator/core/simulation_engine.py new file mode 100644 index 0000000..90e290f --- /dev/null +++ b/scenario_simulator/core/simulation_engine.py @@ -0,0 +1,109 @@ +""" +Core module for radar signal simulation. + +This module provides the classes and functions necessary to generate +synthetic I/Q data for specified radar configurations and target scenarios. +""" + +from dataclasses import dataclass +import numpy as np +from scipy.constants import c + +@dataclass +class RadarConfig: + """ + Stores the configuration parameters for the simulated radar. + + Attributes: + carrier_frequency (float): Carrier frequency in Hertz (Hz). + prf (float): Pulse Repetition Frequency in Hertz (Hz). + pulse_width (float): Duration of a single radar pulse in seconds (s). + sample_rate (float): Sampling rate of the ADC in samples per second (Hz). + """ + carrier_frequency: float + prf: float + pulse_width: float + sample_rate: float + +@dataclass +class Target: + """ + Represents a single point target in the simulation. + + Attributes: + initial_position (np.ndarray): 3D initial position [x, y, z] in meters (m). + velocity (np.ndarray): 3D constant velocity [vx, vy, vz] in m/s. + rcs (float): Radar Cross Section in square meters (m^2). + """ + initial_position: np.ndarray + velocity: np.ndarray + rcs: float + +def generate_iq_data(config: RadarConfig, target: Target, num_pulses: int) -> np.ndarray: + """ + Generates the I/Q data matrix for a single target scenario. + + This function simulates the received echo from a single point target with + constant velocity over a specified number of pulses (Coherent Processing + Interval - CPI). + + Args: + config: The radar configuration parameters. + target: The target's properties. + num_pulses: The total number of pulses to simulate. + + Returns: + A 2D numpy array of complex numbers representing the I/Q data. + Shape: (num_pulses, num_samples_per_pulse). + """ + # --- Derived parameters calculation + wavelength = c / config.carrier_frequency + pri = 1.0 / config.prf # Pulse Repetition Interval + + # Calculate the number of samples within one pulse's duration + num_samples_per_pulse = int(config.pulse_width * config.sample_rate) + + # --- Initialize output data structure + # This matrix will store the complex I/Q data for each pulse + iq_matrix = np.zeros((num_pulses, num_samples_per_pulse), dtype=np.complex128) + + # --- Main simulation loop (over slow-time) + for pulse_index in range(num_pulses): + # Current time relative to the start of the CPI + current_slow_time = pulse_index * pri + + # Update target position based on its constant velocity + current_position = target.initial_position + target.velocity * current_slow_time + + # Calculate range from radar (at origin) to target + range_to_target = np.linalg.norm(current_position) + + # Calculate the two-way time delay for the echo + time_delay = 2 * range_to_target / c + + # Simplified signal amplitude based on radar range equation (power ~ 1/R^4) + # We use sqrt(rcs) because we are modeling voltage/amplitude, not power. + amplitude = np.sqrt(target.rcs) / (range_to_target**2) + + # The core of the simulation: calculate the complex phase shift + # This phase is determined by the round-trip path length in wavelengths + phase_shift = np.exp(-1j * 4 * np.pi * range_to_target / wavelength) + + signal_complex_amplitude = amplitude * phase_shift + + # Model a simple rectangular pulse + pulse_shape = np.ones(num_samples_per_pulse) + received_pulse = signal_complex_amplitude * pulse_shape + + # --- Place the received pulse into the I/Q matrix at the correct time delay + start_sample = int(round(time_delay * config.sample_rate)) + + if 0 <= start_sample < iq_matrix.shape[1]: + # This handles cases where the pulse might partially go out of the sampling window + samples_to_write = min(num_samples_per_pulse, iq_matrix.shape[1] - start_sample) + + iq_matrix[ + pulse_index, start_sample : start_sample + samples_to_write + ] = received_pulse[:samples_to_write] + + return iq_matrix \ No newline at end of file