""" Utility functions for radar-related mathematical calculations. """ import numpy as np from scipy.constants import c # --- Conversion Constants --- METERS_PER_NAUTICAL_MILE = 1852.0 METERS_PER_SECOND_TO_KNOTS = 1.94384 def calculate_max_unambiguous_range(prf: float) -> float: """ Calculates the maximum unambiguous range for a given PRF. Args: prf: Pulse Repetition Frequency in Hertz (Hz). Returns: The maximum unambiguous range in meters (m). """ if prf <= 0: return float('inf') return c / (2 * prf) def calculate_max_unambiguous_velocity(carrier_frequency: float, prf: float) -> float: """ Calculates the maximum unambiguous velocity for a given radar configuration. Args: carrier_frequency: Carrier frequency in Hertz (Hz). prf: Pulse Repetition Frequency in Hertz (Hz). Returns: The maximum unambiguous (Nyquist) velocity in m/s. """ if carrier_frequency <= 0: return float('inf') wavelength = c / carrier_frequency return (prf * wavelength) / 4 def calculate_dwell_time(beamwidth_deg: float, scan_speed_deg_s: float) -> float: """ Calculates the time a target spends within the antenna's beam. Args: beamwidth_deg: The 3dB beamwidth of the antenna in degrees. scan_speed_deg_s: The angular scan speed of the antenna in degrees/sec. Returns: The dwell time in seconds (s). """ if scan_speed_deg_s <= 0: return float('inf') # Staring mode return beamwidth_deg / scan_speed_deg_s def calculate_pulses_on_target(dwell_time_s: float, prf: float) -> int: """ Calculates the number of pulses that hit a target during the dwell time. Args: dwell_time_s: The dwell time in seconds. prf: The Pulse Repetition Frequency in Hz. Returns: The number of pulses hitting the target. """ if np.isinf(dwell_time_s): return -1 # Represents continuous illumination (staring) return int(dwell_time_s * prf) def calculate_gaussian_gain(angle_off_boresight_deg: float, beamwidth_deg: float) -> float: """ Calculates antenna gain based on a Gaussian beam shape model. This models the two-way (transmit and receive) gain pattern. Args: angle_off_boresight_deg: Angle between target and antenna centerline (degrees). beamwidth_deg: The 3dB one-way beamwidth of the antenna in degrees. Returns: A dimensionless gain factor (from 0.0 to 1.0). """ # The standard deviation (sigma) of the Gaussian beam is related to the 3dB beamwidth sigma = beamwidth_deg / (2 * np.sqrt(2 * np.log(2))) # We use angle^2 / (2 * sigma^2) for the one-way power gain pattern. # The two-way voltage gain is the square of the one-way voltage gain, # which is equivalent to the one-way power gain. gain = np.exp(-(angle_off_boresight_deg**2) / (sigma**2)) return gain # --- Unit Conversion Functions --- def meters_to_nm(meters: float) -> float: """Converts meters to nautical miles.""" return meters / METERS_PER_NAUTICAL_MILE def nm_to_meters(nm: float) -> float: """Converts nautical miles to meters.""" return nm * METERS_PER_NAUTICAL_MILE def mps_to_knots(mps: float) -> float: """Converts meters per second to knots.""" return mps * METERS_PER_SECOND_TO_KNOTS def knots_to_mps(knots: float) -> float: """Converts knots to meters per second.""" return knots / METERS_PER_SECOND_TO_KNOTS def cartesian_to_spherical(x: float, y: float, z: float) -> tuple[float, float, float]: """ Converts Cartesian coordinates (x, y, z) to Spherical coordinates. Args: x, y, z: Coordinates in meters. Returns: A tuple containing (range_m, azimuth_deg, elevation_deg). """ range_m = np.sqrt(x**2 + y**2 + z**2) if range_m == 0: return 0.0, 0.0, 0.0 azimuth_deg = np.rad2deg(np.arctan2(y, x)) elevation_deg = np.rad2deg(np.arcsin(z / range_m)) return range_m, azimuth_deg, elevation_deg def spherical_to_cartesian(range_m: float, azimuth_deg: float, elevation_deg: float) -> list[float]: """ Converts Spherical coordinates to Cartesian coordinates (x, y, z). Args: range_m: Range in meters. azimuth_deg: Azimuth in degrees. elevation_deg: Elevation in degrees. Returns: A list containing [x, y, z] coordinates in meters. """ az_rad = np.deg2rad(azimuth_deg) el_rad = np.deg2rad(elevation_deg) x = range_m * np.cos(el_rad) * np.cos(az_rad) y = range_m * np.cos(el_rad) * np.sin(az_rad) z = range_m * np.sin(el_rad) return [x, y, z]