This commit is contained in:
robinson
2026-09-03 15:50:33 +08:00
commit 0d2e580f12
49 changed files with 4310 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Compare Voronoi+boustrophedon vs Voronoi+mTSP patrol planners."""
from __future__ import annotations
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
from uavsearch.geometry import RectRegion
from uavsearch.partition import generate_uneven_positions
from uavsearch.planner import plan_boustrophedon_patrol, plan_mtsp_patrol
from uavsearch.sim import PatrolSimulator, compare_planners
def main() -> None:
parser = argparse.ArgumentParser(description="Compare patrol planners")
parser.add_argument("--width", type=float, default=1000.0)
parser.add_argument("--height", type=float, default=800.0)
parser.add_argument("--uavs", type=int, default=4)
parser.add_argument("--radius", type=float, default=50.0)
parser.add_argument("--speed", type=float, default=15.0)
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--duration", type=float, default=600.0)
parser.add_argument("--grid-density", type=float, default=4.0)
parser.add_argument("--save", type=str, default=None)
args = parser.parse_args()
region = RectRegion.from_size(args.width, args.height)
sweep_width = 2 * args.radius
initial_positions = generate_uneven_positions(region, args.uavs, seed=args.seed)
strip_paths, strip_partition = plan_boustrophedon_patrol(
region, args.uavs, sweep_width, initial_positions=initial_positions
)
mtsp_paths, mtsp_partition = plan_mtsp_patrol(
region, args.uavs, args.radius, initial_positions=initial_positions
)
sim_strip = PatrolSimulator(region, strip_paths, args.speed, args.radius, grid_density=args.grid_density)
sim_mtsp = PatrolSimulator(region, mtsp_paths, args.speed, args.radius, grid_density=args.grid_density)
m_strip = sim_strip.run(args.duration)
m_mtsp = sim_mtsp.run(args.duration)
print("Uneven initial positions -> uniform partitions:")
for cell in strip_partition.cells:
ix, iy = cell.initial_position
print(f" UAV {cell.uav_id} at ({ix:.1f}, {iy:.1f}) -> partition {cell.partition_id}")
print()
print("=== Uniform + Boustrophedon ===")
print(m_strip.summary())
print(f"Load balance: {sim_strip.load_balance_ratio():.2f}")
print()
print("=== Uniform + mTSP ===")
print(m_mtsp.summary())
print(f"Load balance: {sim_mtsp.load_balance_ratio():.2f}")
print()
ratio_strip = m_strip.max_revisit_time / max(m_strip.mean_revisit_time, 1e-9)
ratio_mtsp = m_mtsp.max_revisit_time / max(m_mtsp.mean_revisit_time, 1e-9)
print(f"T_max/T_mean — Boustrophedon: {ratio_strip:.2f}, mTSP: {ratio_mtsp:.2f}")
fig = compare_planners(
region,
strip_paths,
mtsp_paths,
args.speed,
args.radius,
args.duration,
strip_partition=strip_partition,
mtsp_partition=mtsp_partition,
)
if args.save:
out = Path(args.save)
out.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(out, dpi=150, bbox_inches="tight")
print(f"Saved comparison to {out}")
else:
plt.show()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Evaluate a fixed deployment (no optimization)."""
from __future__ import annotations
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
from uavsearch.geometry import RectRegion
from uavsearch.problem import CoverageProblem, evaluate_deployment, random_deployment
from uavsearch.sim import PatrolVisualizer
def main() -> None:
parser = argparse.ArgumentParser(description="Evaluate one UAV deployment")
parser.add_argument("--width", type=float, default=1000.0)
parser.add_argument("--height", type=float, default=800.0)
parser.add_argument("--uavs", type=int, default=4)
parser.add_argument("--radius", type=float, default=50.0)
parser.add_argument("--speed", type=float, default=15.0)
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--partition", choices=["uniform", "voronoi"], default="uniform")
parser.add_argument("--grid-density", type=float, default=4.0)
parser.add_argument("--save", type=str, default=None)
args = parser.parse_args()
region = RectRegion.from_size(args.width, args.height)
problem = CoverageProblem(
region=region,
n_uavs=args.uavs,
sensor_radius=args.radius,
speed=args.speed,
partition_mode=args.partition,
grid_density=args.grid_density,
)
positions = random_deployment(problem, seed=args.seed)
result = evaluate_deployment(problem, positions)
print(f"Objective T_max = {result.objective_value:.2f}s")
print(result.summary_line())
from uavsearch.sim import PatrolSimulator
sim = PatrolSimulator(
region, result.solution.paths, args.speed, args.radius, grid_density=args.grid_density
)
sim.run(result.max_patrol_period * 2.5)
viz = PatrolVisualizer(region)
fig = viz.plot_simulation_snapshot(
sim,
result.solution.paths,
result.solution.metrics,
title="Deployment Evaluation",
partition=result.solution.partition,
)
if args.save:
Path(args.save).parent.mkdir(parents=True, exist_ok=True)
fig.savefig(args.save, dpi=150, bbox_inches="tight")
else:
plt.show()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""
Find UAV deployment positions that minimize the maximum coverage period T_max.
Reads settings from config.toml (project root by default).
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
import matplotlib.pyplot as plt
from uavsearch import evaluate_deployment, load_config, optimize_deployment
from uavsearch.config import AppConfig
from uavsearch.problem import random_deployment
from uavsearch.sim import PatrolSimulator, PatrolVisualizer
def run(cfg: AppConfig) -> None:
problem = cfg.problem.build_problem(cfg.objective, cfg.planner, cfg.simulation)
p = cfg.problem
show_progress = cfg.simulation.show_progress
opt_sim = cfg.simulation.effective_opt_duration()
print("=== Problem ===", flush=True)
print(f"Region {p.width:.0f}x{p.height:.0f} m, N={p.uavs}, r={p.radius:.0f} m", flush=True)
print("Objective: minimize T_max (worst-point revisit period)", flush=True)
print(f"Partition: {p.partition}, planner: {p.planner}", flush=True)
print(
f"Method: {cfg.optimizer.method}, maxiter={cfg.optimizer.maxiter}, popsize={cfg.optimizer.popsize}",
flush=True,
)
print(flush=True)
print("=== Baseline (random deployment) ===", flush=True)
baseline_pos = random_deployment(problem, seed=p.seed)
baseline = evaluate_deployment(
problem, baseline_pos, sim_duration=cfg.simulation.duration, show_progress=show_progress
)
print(baseline.summary_line(), flush=True)
print(flush=True)
if not cfg.run.optimize:
print("run.optimize=false, skipping optimization.", flush=True)
return
print("=== Optimizing ===", flush=True)
opt = optimize_deployment(
problem,
method=cfg.optimizer.method,
seed=p.seed,
maxiter=cfg.optimizer.maxiter,
popsize=cfg.optimizer.popsize,
sim_duration=opt_sim,
initial_guess=baseline_pos,
show_progress=show_progress,
mapso_params=cfg.optimizer.mapso,
)
best = opt.best
print(f"Evaluations: {opt.n_evaluations}", flush=True)
print(best.summary_line(), flush=True)
improve = (baseline.objective_value - best.objective_value) / max(baseline.objective_value, 1e-9)
print(f"T_max: {baseline.objective_value:.1f}s -> {best.objective_value:.1f}s ({improve:+.1%})", flush=True)
_print_positions("Baseline:", baseline.solution.initial_positions)
_print_positions("Optimized:", best.solution.initial_positions)
print("\n=== Rendering figures ===", flush=True)
region = problem.region
viz = PatrolVisualizer(region)
fig, axes = plt.subplots(2, 3, figsize=(22, 13))
viz.plot_partition(baseline.solution.partition, ax=axes[0, 0], title="Baseline partition")
viz.plot_paths(
baseline.solution.paths,
ax=axes[0, 1],
title=f"Baseline patrol\nT_max={baseline.max_revisit_time:.1f}s",
partition=baseline.solution.partition,
)
sim_b = PatrolSimulator(
region, baseline.solution.paths, p.speed, p.radius,
grid_density=p.grid_density, depot_service_time=p.depot_service,
)
sim_b.run(baseline.max_patrol_period * 3.0, show_progress=show_progress)
viz.plot_heatmap(sim_b.grid.idle_map(), ax=axes[0, 2], title="Baseline idle time")
viz.plot_partition(best.solution.partition, ax=axes[1, 0], title="Optimized partition")
viz.plot_paths(
best.solution.paths,
ax=axes[1, 1],
title=f"Optimized patrol\nT_max={best.max_revisit_time:.1f}s",
partition=best.solution.partition,
)
sim_o = PatrolSimulator(
region, best.solution.paths, p.speed, p.radius,
grid_density=p.grid_density, depot_service_time=p.depot_service,
)
sim_o.run(best.max_patrol_period * 3.0, show_progress=show_progress)
viz.plot_heatmap(sim_o.grid.idle_map(), ax=axes[1, 2], title="Optimized idle time")
fig.suptitle(
f"Coverage Period Minimization — T_max {baseline.objective_value:.1f}s -> {best.objective_value:.1f}s",
fontsize=14,
)
fig.tight_layout()
if cfg.run.save:
out = Path(cfg.run.save)
out.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(out, dpi=150, bbox_inches="tight")
print(f"Saved to {out}", flush=True)
elif cfg.run.show:
plt.show()
else:
plt.close(fig)
def _print_positions(label: str, positions) -> None:
print(label, flush=True)
for i, (x, y) in enumerate(positions):
print(f" UAV {i}: ({x:.1f}, {y:.1f})", flush=True)
def main() -> None:
parser = argparse.ArgumentParser(description="Minimize T_max (config.toml driven)")
parser.add_argument("--config", type=str, default="config.toml", help="Path to TOML config")
args = parser.parse_args()
run(load_config(args.config))
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nInterrupted.", file=sys.stderr)
sys.exit(130)

View File

@@ -0,0 +1,204 @@
#!/usr/bin/env python3
"""
Visual dashboard for multi-UAV coverage-period minimization.
Reads settings from config.toml (project root by default).
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from uavsearch import CoverageProblem, evaluate_deployment, load_config, optimize_deployment
from uavsearch.config import AppConfig
from uavsearch.problem import random_deployment
from uavsearch.viz import plot_coverage_dashboard
def run(cfg: AppConfig) -> None:
problem = cfg.problem.build_problem(cfg.objective, cfg.planner, cfg.simulation)
opt_cfg = cfg.optimizer
show_progress = cfg.simulation.show_progress
baseline_pos = random_deployment(problem, seed=cfg.problem.seed)
print("Evaluating baseline deployment...", flush=True)
baseline = evaluate_deployment(
problem, baseline_pos, sim_duration=cfg.simulation.duration, show_progress=show_progress
)
print(baseline.summary_line(), flush=True)
opt_result = None
optimized = baseline
opt_sim = cfg.simulation.effective_opt_duration()
if cfg.run.optimize:
method = cfg.optimizer.method
print(f"Optimizing ({method})...", flush=True)
print(
f" 搜索仿真时长: {opt_sim:.0f}s/次 (fast_eval), 最终报告: {cfg.simulation.duration:.0f}s",
flush=True,
)
opt_result = optimize_deployment(
problem,
method=method,
seed=cfg.problem.seed,
maxiter=cfg.optimizer.maxiter,
popsize=cfg.optimizer.popsize,
sim_duration=opt_sim,
initial_guess=baseline_pos,
show_progress=False,
mapso_params=cfg.optimizer.mapso,
)
optimized = opt_result.best
print(optimized.summary_line(), flush=True)
delta = baseline.objective_value - optimized.objective_value
moved = float(
np.max(np.linalg.norm(optimized.solution.initial_positions - baseline.solution.initial_positions, axis=1))
)
print(f" T_max change: {delta:+.2f}s | max depot move: {moved:.1f} m", flush=True)
print("Building dashboard...", flush=True)
fig = plot_coverage_dashboard(
problem,
baseline=baseline,
optimized=optimized,
opt_result=opt_result,
show_progress=show_progress,
)
out = Path(cfg.run.save)
out.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(out, dpi=150, bbox_inches="tight")
print(f"Dashboard saved to {out}", flush=True)
if cfg.run.show:
plt.show()
else:
plt.close(fig)
def main() -> None:
parser = argparse.ArgumentParser(description="Coverage visualization (config.toml driven)")
parser.add_argument(
"--config",
type=str,
default="config.toml",
help="Path to TOML config (default: config.toml in cwd)",
)
args = parser.parse_args()
cfg = load_config(args.config)
run(cfg)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nInterrupted.", file=sys.stderr)
sys.exit(130)