init
This commit is contained in:
84
examples/compare_planners.py
Normal file
84
examples/compare_planners.py
Normal 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()
|
||||
69
examples/demo_strip_patrol.py
Normal file
69
examples/demo_strip_patrol.py
Normal 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()
|
||||
276
examples/optimize_coverage.py
Normal file
276
examples/optimize_coverage.py
Normal 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)
|
||||
|
||||
204
examples/visualize_coverage.py
Normal file
204
examples/visualize_coverage.py
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user