Skip to content

Commit 9286eff

Browse files
fix: Ruff formatting and Ubuntu Qt dependencies
1 parent a6f40ca commit 9286eff

73 files changed

Lines changed: 1284 additions & 440 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ jobs:
4040
python -m pip install --upgrade pip
4141
pip install -r requirements.txt
4242
43+
- name: Install System Dependencies (Linux)
44+
if: matrix.os == 'ubuntu-latest'
45+
run: |
46+
sudo apt-get update
47+
sudo apt-get install -y libegl1 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0 libgl1-mesa-glx libxcb-cursor0
48+
4349
- uses: Nuitka/Nuitka-Action@main
4450
with:
4551
nuitka-version: main

RELEASE_DRAFT.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## 🛰️ RadarSim v2.4.0: The Cognitive Imaging Update
2+
3+
We are incredibly excited to announce the release of **RadarSim v2.4.0**. This milestone completes **Phase 30** of the RadarSim roadmap and brings massive performance improvements alongside state-of-the-art radar simulation capabilities.
4+
5+
### 🚀 What's New
6+
7+
- **Lightning-Fast Sensor Fusion:** The newly overhauled `NetworkManager` is now capable of fusing 100 simultaneous tracks across a 5-node radar network with a blistering average latency of **7.79ms** using Covariance Intersection.
8+
- **High-Fidelity Imaging Radar (SAR/ISAR):**
9+
- Implemented the vectorized **Range-Doppler Algorithm (RDA)** for Synthetic Aperture Radar (SAR) image reconstruction. Achieves 1.5m range and 0.5m cross-range resolution.
10+
- Added **ISAR Processing** with translational motion compensation for dynamic target profiling.
11+
- **AI Tactical Director (Red Force Agent):**
12+
- An intelligent adversary agent that performs live radar network coverage analysis and blind zone detection.
13+
- Features Low-Pd corridor routing and autonomous DRFM jammer deployment to stress-test your EW operators.
14+
- **C++ Standalone Execution:**
15+
- Migrated build system to **Nuitka**. RadarSim now compiles to optimized native C++ binaries across Windows, Linux, and macOS for maximum performance and security.
16+
- **Scientific Foundation Hardened:**
17+
- Validated Pulse-Doppler and CPI implementation against *Richards (2005)*.
18+
- Aligned the AI cognitive routing logic with concepts from *Haykin (2006)*.
19+
20+
### 📸 Visuals
21+
22+
**SAR Image Reconstruction**
23+
<img src="https://raw.githubusercontent.com/SpaceEngineerSS/RadarSim/main/docs/images/sar_viewer.png" width="600">
24+
25+
**3D Tactical View & Sensor Fusion**
26+
<img src="https://raw.githubusercontent.com/SpaceEngineerSS/RadarSim/main/docs/images/3d_tactical.png" width="600">
27+
28+
**A-Scope (CFAR) & Jamming Strobe Analysis**
29+
<img src="https://raw.githubusercontent.com/SpaceEngineerSS/RadarSim/main/docs/images/a_scope_cfar.png" width="600">
30+
31+
---
32+
33+
*Developed by Mehmet Gümüş (@SpaceEngineerSS) - RadarSim v2.x*

batch_run.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@
3535
# Add project root to path
3636
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
3737

38-
from src.simulation.headless_runner import SimulationConfig, SimulationResult, run_single_simulation
38+
from src.simulation.headless_runner import (
39+
SimulationConfig,
40+
SimulationResult,
41+
run_single_simulation,
42+
)
3943
from src.simulation.scenario_generator import ParameterSpace, ScenarioGenerator
4044

4145

@@ -83,7 +87,9 @@ def run_batch(
8387
else:
8488
# Without progress bar
8589
print("Running simulations...")
86-
for i, result in enumerate(pool.imap_unordered(run_single_simulation, configs)):
90+
for i, result in enumerate(
91+
pool.imap_unordered(run_single_simulation, configs)
92+
):
8793
results.append(result)
8894
if (i + 1) % 10 == 0:
8995
print(f" Completed: {i + 1}/{len(configs)}")
@@ -146,10 +152,16 @@ def _print_summary(results: List[SimulationResult], total_time: float) -> None:
146152
def main():
147153
parser = argparse.ArgumentParser(description="Run Monte Carlo radar simulations")
148154
parser.add_argument(
149-
"--configs", type=int, default=None, help="Number of configurations (default: auto)"
155+
"--configs",
156+
type=int,
157+
default=None,
158+
help="Number of configurations (default: auto)",
150159
)
151160
parser.add_argument(
152-
"--runs", type=int, default=5, help="Monte Carlo runs per configuration (default: 5)"
161+
"--runs",
162+
type=int,
163+
default=5,
164+
help="Monte Carlo runs per configuration (default: 5)",
153165
)
154166
parser.add_argument(
155167
"--workers",
@@ -194,7 +206,9 @@ def main():
194206
configs = configs[: args.configs]
195207

196208
# Run batch
197-
results = run_batch(configs=configs, n_workers=args.workers, output_file=args.output)
209+
results = run_batch(
210+
configs=configs, n_workers=args.workers, output_file=args.output
211+
)
198212

199213
return 0
200214

benchmarks/network_manager_benchmark.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@
88

99
from src.simulation.network_manager import NetworkManager, RadarNode, NetworkTrack
1010

11+
1112
def run_benchmark():
1213
print("=" * 50)
1314
print("NetworkManager Performance Benchmark")
1415
print("=" * 50)
1516

1617
# Initialize a 5-node network
1718
manager = NetworkManager()
18-
19+
1920
# Create 5 radar nodes
2021
nodes = []
2122
node_ids = []
2223
for i in range(5):
2324
nid = f"radar_{i}"
24-
node = manager.register_node(nid, position_xy=np.array([i*1000, i*1000]))
25+
node = manager.register_node(nid, position_xy=np.array([i * 1000, i * 1000]))
2526
nodes.append(node)
2627
node_ids.append(nid)
2728

@@ -33,19 +34,23 @@ def run_benchmark():
3334
track = NetworkTrack(
3435
track_id=f"{nid}_track_{j}",
3536
node_id=nid,
36-
state=np.array([np.random.uniform(-50000, 50000),
37-
np.random.uniform(-50000, 50000),
38-
np.random.uniform(-300, 300),
39-
np.random.uniform(-300, 300)]),
37+
state=np.array(
38+
[
39+
np.random.uniform(-50000, 50000),
40+
np.random.uniform(-50000, 50000),
41+
np.random.uniform(-300, 300),
42+
np.random.uniform(-300, 300),
43+
]
44+
),
4045
covariance=np.eye(4) * 100,
4146
timestamp=time.time(),
42-
snr_db=20.0
47+
snr_db=20.0,
4348
)
4449
tracks.append(track)
4550
manager.submit_tracks(nid, tracks, current_time=1.0)
4651

4752
print("Simulating T2TA for 5-node network (100 tracks total)...")
48-
53+
4954
# Run association 100 times to get average latency
5055
latencies = []
5156
for _ in range(100):
@@ -57,16 +62,17 @@ def run_benchmark():
5762

5863
avg_latency = np.mean(latencies)
5964
max_latency = np.max(latencies)
60-
65+
6166
print(f"Average Latency: {avg_latency:.2f} ms")
6267
print(f"Max Latency: {max_latency:.2f} ms")
63-
68+
6469
if avg_latency > 10.0:
6570
print("\nWARNING: Latency exceeds 10ms threshold!")
6671
sys.exit(1)
6772
else:
6873
print("\nSUCCESS: Latency is within 10ms threshold.")
6974
sys.exit(0)
7075

76+
7177
if __name__ == "__main__":
7278
run_benchmark()

capture_screenshots.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ def capture_analysis():
108108

109109
# Capture each tab
110110
QTimer.singleShot(
111-
500, lambda: take_screenshot("recording_analysis", window.analysis_window)
111+
500,
112+
lambda: take_screenshot(
113+
"recording_analysis", window.analysis_window
114+
),
112115
)
113116

114117
def capture_ambiguity():
@@ -155,7 +158,9 @@ def capture_sar():
155158
sar_dialog = SARViewer(window)
156159
sar_dialog.show()
157160
sar_dialog.resize(700, 600)
158-
QTimer.singleShot(1000, lambda: take_screenshot("sar_viewer", sar_dialog))
161+
QTimer.singleShot(
162+
1000, lambda: take_screenshot("sar_viewer", sar_dialog)
163+
)
159164
except Exception as e:
160165
print(f" SAR capture failed: {e}")
161166

examples/api_examples.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def example_basic_radar():
4848
doppler_hz = physics.doppler_shift(target, radar_pos, radar_vel)
4949

5050
print("=== Basic Radar Example ===")
51-
print(f"Target Range: {range_m/1000:.1f} km")
51+
print(f"Target Range: {range_m / 1000:.1f} km")
5252
print(f"SNR: {snr_db:.1f} dB")
5353
print(f"Doppler Shift: {doppler_hz:.0f} Hz")
5454
print(f"Radial Velocity: {doppler_hz * radar_params.wavelength / 2:.1f} m/s")
@@ -141,7 +141,7 @@ def example_ekf_tracking():
141141
for step in range(5):
142142
# Predict
143143
track = ekf.predict(track)
144-
print(f"Step {step+1} - Predicted X: {track.position[0]:.1f} m")
144+
print(f"Step {step + 1} - Predicted X: {track.position[0]:.1f} m")
145145

146146

147147
def example_ecm_simulation():
@@ -156,12 +156,16 @@ def example_ecm_simulation():
156156
print("\n=== ECM Simulation Example ===")
157157

158158
# Setup
159-
radar_params = RadarParameters(frequency=10e9, power_transmitted=1000, antenna_gain_tx=30)
159+
radar_params = RadarParameters(
160+
frequency=10e9, power_transmitted=1000, antenna_gain_tx=30
161+
)
160162
physics = RadarPhysics(radar_params)
161163
ecm = ECMSimulator(physics)
162164

163165
target = TargetParameters(
164-
position=np.array([15000.0, 0.0, 5000.0]), velocity=np.array([-150.0, 0.0, 0.0]), rcs=10.0
166+
position=np.array([15000.0, 0.0, 5000.0]),
167+
velocity=np.array([-150.0, 0.0, 0.0]),
168+
rcs=10.0,
165169
)
166170

167171
radar_pos = np.array([0.0, 0.0, 0.0])
@@ -172,7 +176,9 @@ def example_ecm_simulation():
172176

173177
# Activate noise jamming
174178
ecm.activate_noise_jamming(
175-
jammer_position=np.array([14000.0, 0.0, 5000.0]), jammer_power=500.0, frequency_offset=0.0
179+
jammer_position=np.array([14000.0, 0.0, 5000.0]),
180+
jammer_power=500.0,
181+
frequency_offset=0.0,
176182
)
177183

178184
# Calculate jammed signal
@@ -182,7 +188,9 @@ def example_ecm_simulation():
182188

183189
# Jamming effectiveness
184190
jamming_loss_db = (
185-
10 * np.log10(received_power / jammed_power) if jammed_power > 0 else float("inf")
191+
10 * np.log10(received_power / jammed_power)
192+
if jammed_power > 0
193+
else float("inf")
186194
)
187195
print(f"Jamming Loss: {jamming_loss_db:.1f} dB")
188196

@@ -191,7 +199,7 @@ def example_ecm_simulation():
191199

192200
for t in range(5):
193201
offset = ecm.update_rgpo(1.0)
194-
print(f"RGPO t={t+1}s: Range Offset = {offset:.0f} m")
202+
print(f"RGPO t={t + 1}s: Range Offset = {offset:.0f} m")
195203

196204

197205
def example_cfar_detection():

headless.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def load_config_from_yaml(filepath: str) -> SimulationConfig:
3636

3737
radar = data.get("radar", {})
3838
target = data.get(
39-
"target", data.get("targets", [{}])[0] if isinstance(data.get("targets"), list) else {}
39+
"target",
40+
data.get("targets", [{}])[0] if isinstance(data.get("targets"), list) else {},
4041
)
4142
sim = data.get("simulation", {})
4243

@@ -64,31 +65,47 @@ def main():
6465
parser = argparse.ArgumentParser(description="Run headless radar simulation")
6566

6667
# Config file
67-
parser.add_argument("--config", type=str, default=None, help="YAML configuration file")
68+
parser.add_argument(
69+
"--config", type=str, default=None, help="YAML configuration file"
70+
)
6871

6972
# Target parameters
7073
parser.add_argument(
7174
"--range", type=float, default=50.0, help="Target range in km (default: 50)"
7275
)
73-
parser.add_argument("--rcs", type=float, default=1.0, help="Target RCS in m² (default: 1.0)")
7476
parser.add_argument(
75-
"--velocity", type=float, default=0.0, help="Target radial velocity in m/s (default: 0)"
77+
"--rcs", type=float, default=1.0, help="Target RCS in m² (default: 1.0)"
78+
)
79+
parser.add_argument(
80+
"--velocity",
81+
type=float,
82+
default=0.0,
83+
help="Target radial velocity in m/s (default: 0)",
7684
)
7785

7886
# Radar parameters
7987
parser.add_argument(
80-
"--frequency", type=float, default=10.0, help="Radar frequency in GHz (default: 10)"
88+
"--frequency",
89+
type=float,
90+
default=10.0,
91+
help="Radar frequency in GHz (default: 10)",
8192
)
8293
parser.add_argument(
8394
"--power", type=float, default=100.0, help="Transmit power in kW (default: 100)"
8495
)
8596

8697
# Simulation parameters
8798
parser.add_argument(
88-
"--duration", type=float, default=10.0, help="Simulation duration in seconds (default: 10)"
99+
"--duration",
100+
type=float,
101+
default=10.0,
102+
help="Simulation duration in seconds (default: 10)",
89103
)
90104
parser.add_argument(
91-
"--threshold", type=float, default=13.0, help="Detection threshold in dB (default: 13)"
105+
"--threshold",
106+
type=float,
107+
default=13.0,
108+
help="Detection threshold in dB (default: 13)",
92109
)
93110

94111
# Options

plot_performance.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,15 @@ def plot_pd_vs_snr(df: "pd.DataFrame", save_path: Optional[str] = None) -> None:
106106
# Extract bin centers
107107
bin_centers = [interval.mid for interval in grouped.index]
108108

109-
ax.plot(bin_centers, grouped.values, "o-", color="#00d4ff", linewidth=2, markersize=8)
109+
ax.plot(
110+
bin_centers, grouped.values, "o-", color="#00d4ff", linewidth=2, markersize=8
111+
)
110112

111113
# Add threshold line
112114
threshold = df["threshold_db"].iloc[0] if "threshold_db" in df.columns else 13
113-
ax.axvline(threshold, color="red", linestyle="--", label=f"Threshold = {threshold} dB")
115+
ax.axvline(
116+
threshold, color="red", linestyle="--", label=f"Threshold = {threshold} dB"
117+
)
114118

115119
ax.set_xlabel("Signal-to-Noise Ratio (dB)", fontsize=12)
116120
ax.set_ylabel("Probability of Detection ($P_d$)", fontsize=12)
@@ -136,13 +140,20 @@ def plot_snr_heatmap(df: "pd.DataFrame", save_path: Optional[str] = None) -> Non
136140
fig, ax = plt.subplots(figsize=(10, 6))
137141

138142
# Pivot table
139-
pivot = df.pivot_table(values="mean_snr_db", index="rcs_m2", columns="range_km", aggfunc="mean")
143+
pivot = df.pivot_table(
144+
values="mean_snr_db", index="rcs_m2", columns="range_km", aggfunc="mean"
145+
)
140146

141147
im = ax.imshow(
142148
pivot.values,
143149
aspect="auto",
144150
cmap="plasma",
145-
extent=[pivot.columns.min(), pivot.columns.max(), pivot.index.min(), pivot.index.max()],
151+
extent=[
152+
pivot.columns.min(),
153+
pivot.columns.max(),
154+
pivot.index.min(),
155+
pivot.index.max(),
156+
],
146157
origin="lower",
147158
)
148159

@@ -207,7 +218,9 @@ def generate_report(df: "pd.DataFrame", save_path: str) -> None:
207218

208219

209220
def main():
210-
parser = argparse.ArgumentParser(description="Plot radar performance from batch results")
221+
parser = argparse.ArgumentParser(
222+
description="Plot radar performance from batch results"
223+
)
211224
parser.add_argument("input", type=str, help="Input CSV file from batch_run.py")
212225
parser.add_argument(
213226
"--save",

0 commit comments

Comments
 (0)