agi-app-uav-relay-queue 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agi_app_uav_relay_queue/__init__.py +33 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/README.md +64 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/lab_stages.toml +4 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/notebook_export.toml +26 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/pipeline_view.dot +13 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/pyproject.toml +13 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/service_templates/train_then_serve_policy_run.json +65 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/app_args_form.py +163 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/app_settings.toml +54 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/connectors/data_connectors.toml +27 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/pre_prompt.json +1 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/__init__.py +30 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/app_args.py +94 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/reduction.py +204 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/sample_data/uav_queue_hotspot.json +50 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/uav_relay_queue.py +143 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/uav_relay_queue_alias.py +5 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/uav_relay_queue_args.py +23 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue_worker/__init__.py +5 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue_worker/pyproject.toml +13 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue_worker/uav_relay_queue_worker.py +713 -0
- agi_app_uav_relay_queue/project/uav_relay_queue_project/uv_config.toml +5 -0
- agi_app_uav_relay_queue-0.1.0.dist-info/METADATA +49 -0
- agi_app_uav_relay_queue-0.1.0.dist-info/RECORD +28 -0
- agi_app_uav_relay_queue-0.1.0.dist-info/WHEEL +5 -0
- agi_app_uav_relay_queue-0.1.0.dist-info/entry_points.txt +3 -0
- agi_app_uav_relay_queue-0.1.0.dist-info/licenses/LICENSE +22 -0
- agi_app_uav_relay_queue-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Installed AGILAB app project provider for uav_relay_queue_project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
APP_SLUG = 'uav_relay_queue'
|
|
8
|
+
PROJECT_NAME = 'uav_relay_queue_project'
|
|
9
|
+
PACKAGE_NAME = 'agi-app-uav-relay-queue'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def package_root() -> Path:
|
|
13
|
+
return Path(__file__).resolve().parent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def project_root() -> Path:
|
|
17
|
+
packaged_root = package_root() / "project" / PROJECT_NAME
|
|
18
|
+
if packaged_root.exists():
|
|
19
|
+
return packaged_root
|
|
20
|
+
source_root = Path(__file__).resolve().parents[4] / "apps" / "builtin" / PROJECT_NAME
|
|
21
|
+
return source_root if source_root.exists() else packaged_root
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def metadata() -> dict[str, str]:
|
|
25
|
+
return {
|
|
26
|
+
"slug": APP_SLUG,
|
|
27
|
+
"project": PROJECT_NAME,
|
|
28
|
+
"package": PACKAGE_NAME,
|
|
29
|
+
"project_root": str(project_root()),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ["APP_SLUG", "PACKAGE_NAME", "PROJECT_NAME", "metadata", "package_root", "project_root"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# UAV Relay Queue Project
|
|
2
|
+
|
|
3
|
+
`uav_relay_queue_project` is the AGILAB install id for this built-in lightweight UAV
|
|
4
|
+
relay queue example.
|
|
5
|
+
|
|
6
|
+
The project demonstrates a compact routing-and-queueing simulation:
|
|
7
|
+
- one UAV source
|
|
8
|
+
- one ground sink
|
|
9
|
+
- two relay choices with different queue and delay trade-offs
|
|
10
|
+
- exported telemetry that can be inspected in AGILAB analysis pages
|
|
11
|
+
|
|
12
|
+
Origin note:
|
|
13
|
+
|
|
14
|
+
- this built-in example is conceptually inspired by the SimPy buffer-based
|
|
15
|
+
queueing pattern described in
|
|
16
|
+
[`UavNetSim`](https://github.com/Zihao-Felix-Zhou/UavNetSim)
|
|
17
|
+
(MIT-licensed)
|
|
18
|
+
- it does not vendor or claim to adapt UavNetSim source code directly
|
|
19
|
+
|
|
20
|
+
## What it is good for
|
|
21
|
+
|
|
22
|
+
- a self-contained AGILAB demo app
|
|
23
|
+
- quick queue-aware routing experiments
|
|
24
|
+
- understanding how relay congestion changes packet delivery, delay, and queue depth
|
|
25
|
+
|
|
26
|
+
## What is not implemented in the public version
|
|
27
|
+
|
|
28
|
+
This public built-in example is intentionally lightweight. It does **not** implement:
|
|
29
|
+
- a full external UAV network simulator or emulator backend
|
|
30
|
+
- detailed radio, PHY, or MAC behavior
|
|
31
|
+
- large topology families or operational-scale routing stacks
|
|
32
|
+
- production-grade routing control traffic, interference, or energy models
|
|
33
|
+
- a complete research benchmark for UAV networking
|
|
34
|
+
|
|
35
|
+
The goal is to keep the public example easy to run while still making queue buildup,
|
|
36
|
+
relay choice, delay, and drops visible inside AGILAB.
|
|
37
|
+
|
|
38
|
+
## Main outputs
|
|
39
|
+
|
|
40
|
+
Each run exports:
|
|
41
|
+
- queue time series
|
|
42
|
+
- packet events
|
|
43
|
+
- relay routing summary
|
|
44
|
+
- node positions
|
|
45
|
+
- `pipeline/topology.gml`
|
|
46
|
+
- `pipeline/allocations_steps.csv`
|
|
47
|
+
- trajectory CSVs for `view_maps_network`
|
|
48
|
+
|
|
49
|
+
## Typical flow
|
|
50
|
+
|
|
51
|
+
1. Select `uav_relay_queue_project` in `PROJECT`.
|
|
52
|
+
2. Run it from `ORCHESTRATE`.
|
|
53
|
+
3. Inspect queue artifacts in `view_relay_resilience`.
|
|
54
|
+
4. Inspect topology and trajectories in `view_maps_network`.
|
|
55
|
+
|
|
56
|
+
## What this teases in AGILAB
|
|
57
|
+
|
|
58
|
+
The same framework can support richer network studies than this public demo shows.
|
|
59
|
+
With dedicated apps and pages, AGILAB can be used to:
|
|
60
|
+
- run larger scenario sweeps through `ORCHESTRATE` and `WORKFLOW`
|
|
61
|
+
- attach custom analysis pages to domain-specific artifacts
|
|
62
|
+
- compare routing variants across repeatable experiment runs
|
|
63
|
+
- distribute experiments across workers instead of keeping everything local
|
|
64
|
+
- evolve a lightweight demo into a more advanced simulator-backed workflow
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
uav_relay_queue = [
|
|
2
|
+
{ D = "Seed the UAV relay queue scenario", Q = "Load or seed the compact UAV relay queue scenario and expose two relay paths toward one sink.", M = "", C = "APP = 'uav_relay_queue_project'\nscenario = 'uav_queue_hotspot'\nrouting_policy = 'shortest_path'", R = "runpy" },
|
|
3
|
+
{ D = "Run the relay queue policy", Q = "Execute the chosen relay policy and export packet, queue, and trajectory artifacts for analysis.", M = "", C = "APP = 'uav_relay_queue_project'\nrouting_policy = 'queue_aware'\ndata_in = 'uav_relay_queue/scenarios'\ndata_out = 'uav_relay_queue/results'", R = "runpy" },
|
|
4
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[notebook_export]
|
|
2
|
+
|
|
3
|
+
[[notebook_export.related_pages]]
|
|
4
|
+
module = "view_relay_resilience"
|
|
5
|
+
label = "UAV Relay Queue Analysis"
|
|
6
|
+
description = "Inspect queue depth, packet delivery, relay routing summary, and node positions for the relay-queue demo."
|
|
7
|
+
artifacts = [
|
|
8
|
+
"*_summary_metrics.json",
|
|
9
|
+
"*_queue_timeseries.csv",
|
|
10
|
+
"*_packet_events.csv",
|
|
11
|
+
"*_node_positions.csv",
|
|
12
|
+
"*_routing_summary.csv",
|
|
13
|
+
]
|
|
14
|
+
launch_note = "Open this first for the dedicated relay queue analysis view over the exported run artifacts."
|
|
15
|
+
|
|
16
|
+
[[notebook_export.related_pages]]
|
|
17
|
+
module = "view_maps_network"
|
|
18
|
+
label = "Maps Network"
|
|
19
|
+
description = "Reuse the same relay run as a topology, allocation, and trajectory map."
|
|
20
|
+
artifacts = [
|
|
21
|
+
"pipeline/topology.gml",
|
|
22
|
+
"pipeline/allocations_steps.csv",
|
|
23
|
+
"*_trajectory_summary.json",
|
|
24
|
+
"*_trajectory*.csv",
|
|
25
|
+
]
|
|
26
|
+
launch_note = "Use this when you want the generic topology and trajectory exploration path for the same run."
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
digraph uav_relay_queue_pipeline {
|
|
2
|
+
rankdir=LR;
|
|
3
|
+
graph [fontname="Helvetica", fontsize=10];
|
|
4
|
+
node [shape=box, style="rounded,filled", fillcolor="#F5F7FA", color="#2F4B7C", fontname="Helvetica", fontsize=10];
|
|
5
|
+
edge [color="#4C78A8", arrowsize=0.8];
|
|
6
|
+
|
|
7
|
+
scenario [label="Scenario JSON\nsource + relays + sink"];
|
|
8
|
+
simulate [label="UAV relay queue simulation\nshortest_path | queue_aware"];
|
|
9
|
+
analysis [label="AGILAB ANALYSIS\nview_relay_resilience\nview_maps_network"];
|
|
10
|
+
|
|
11
|
+
scenario -> simulate;
|
|
12
|
+
simulate -> analysis;
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "uav_relay_queue_project"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Built-in AGILAB lightweight UAV relay queue example"
|
|
5
|
+
requires-python = ">=3.13"
|
|
6
|
+
dependencies = ["agi-env>=2026.05.13.post3,<2027.0", "agi-node>=2026.05.13.post3,<2027.0", "agi-cluster>=2026.05.13.post3,<2027.0", "networkx", "pandas>=2.3.0", "pydantic", "simpy>=4.1,<5", "streamlit>=1.56.0"]
|
|
7
|
+
|
|
8
|
+
[tool.setuptools.package-data]
|
|
9
|
+
uav_relay_queue = ["sample_data/*.json"]
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["setuptools"]
|
|
13
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "agilab.example.train_then_serve.v1",
|
|
3
|
+
"training_run": {
|
|
4
|
+
"app": "sb3_trainer_project",
|
|
5
|
+
"trainer": "uav_relay_queue_ppo",
|
|
6
|
+
"run_id": "trainer_uav_relay_queue_ppo_preview",
|
|
7
|
+
"model_artifact": "sb3_trainer/pipeline/trainer_uav_relay_queue_ppo/uav_queue_model.zip",
|
|
8
|
+
"metrics": {
|
|
9
|
+
"delivery_ratio": 0.96,
|
|
10
|
+
"p95_latency_ms": 61.0,
|
|
11
|
+
"reward_mean": 42.5
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"service": {
|
|
15
|
+
"name": "uav_relay_policy_service",
|
|
16
|
+
"version": "2026.05.preview",
|
|
17
|
+
"health_thresholds": {
|
|
18
|
+
"latency_budget_ms": 80.0,
|
|
19
|
+
"allow_idle": false,
|
|
20
|
+
"max_unhealthy": 0,
|
|
21
|
+
"max_restart_rate": 0.25
|
|
22
|
+
},
|
|
23
|
+
"input_schema": {
|
|
24
|
+
"source_queue_depth": "float 0..1",
|
|
25
|
+
"candidate_relays": "list of relay features"
|
|
26
|
+
},
|
|
27
|
+
"output_schema": {
|
|
28
|
+
"selected_relay": "relay id",
|
|
29
|
+
"action": "route_via_relay",
|
|
30
|
+
"confidence": "float 0..1"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"prediction_request": {
|
|
34
|
+
"source_queue_depth": 0.82,
|
|
35
|
+
"candidate_relays": [
|
|
36
|
+
{
|
|
37
|
+
"id": "relay_alpha",
|
|
38
|
+
"latency_ms": 72.0,
|
|
39
|
+
"queue_depth": 0.74,
|
|
40
|
+
"capacity_mbps": 7.5,
|
|
41
|
+
"risk": 0.22
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "relay_beta",
|
|
45
|
+
"latency_ms": 55.0,
|
|
46
|
+
"queue_depth": 0.41,
|
|
47
|
+
"capacity_mbps": 9.4,
|
|
48
|
+
"risk": 0.11
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "relay_gamma",
|
|
52
|
+
"latency_ms": 63.0,
|
|
53
|
+
"queue_depth": 0.35,
|
|
54
|
+
"capacity_mbps": 8.1,
|
|
55
|
+
"risk": 0.08
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"policy_scoring": {
|
|
60
|
+
"capacity_mbps": 0.42,
|
|
61
|
+
"latency_ms": -0.18,
|
|
62
|
+
"queue_depth": -0.28,
|
|
63
|
+
"risk": -0.12
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import streamlit as st
|
|
8
|
+
from pydantic import ValidationError
|
|
9
|
+
|
|
10
|
+
_HERE = Path(__file__).resolve().parent
|
|
11
|
+
if str(_HERE) not in sys.path:
|
|
12
|
+
sys.path.insert(0, str(_HERE))
|
|
13
|
+
|
|
14
|
+
from uav_relay_queue import UavRelayQueueArgs, dump_args, load_args
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
PAGE_ID = "uav_relay_queue_project:app_args_form"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _k(name: str) -> str:
|
|
21
|
+
return f"{PAGE_ID}:{name}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_env():
|
|
25
|
+
env = st.session_state.get("env") or st.session_state.get("_env")
|
|
26
|
+
if env is None:
|
|
27
|
+
st.error("AGILab environment is not initialised yet. Return to the main page and try again.")
|
|
28
|
+
st.stop()
|
|
29
|
+
return env
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _load_current_args(settings_path: Path) -> UavRelayQueueArgs:
|
|
33
|
+
try:
|
|
34
|
+
return load_args(settings_path)
|
|
35
|
+
except Exception as exc:
|
|
36
|
+
st.warning(f"Unable to load UAV relay queue args from `{settings_path}`: {exc}")
|
|
37
|
+
return UavRelayQueueArgs()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
env = _get_env()
|
|
41
|
+
settings_path = Path(env.app_settings_file)
|
|
42
|
+
current_args = _load_current_args(settings_path)
|
|
43
|
+
current_payload = current_args.model_dump(mode="json")
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
share_root = env.share_root_path()
|
|
47
|
+
except Exception:
|
|
48
|
+
share_root = None
|
|
49
|
+
|
|
50
|
+
artifact_root = Path(getattr(env, "AGILAB_EXPORT_ABS", Path.home() / "export")) / env.target / "queue_analysis"
|
|
51
|
+
|
|
52
|
+
st.caption(
|
|
53
|
+
"This built-in app runs a lightweight UAV relay queue simulation with explicit "
|
|
54
|
+
f"packet and queue telemetry. Analysis artifacts are exported to `{artifact_root}`."
|
|
55
|
+
)
|
|
56
|
+
if share_root:
|
|
57
|
+
st.caption(f"Current shared root: `{share_root}`")
|
|
58
|
+
|
|
59
|
+
defaults = {
|
|
60
|
+
"data_in": str(current_payload.get("data_in", "") or ""),
|
|
61
|
+
"data_out": str(current_payload.get("data_out", "") or ""),
|
|
62
|
+
"files": str(current_payload.get("files", "*.json") or "*.json"),
|
|
63
|
+
"nfile": int(current_payload.get("nfile", 1) or 1),
|
|
64
|
+
"routing_policy": str(current_payload.get("routing_policy", "shortest_path") or "shortest_path"),
|
|
65
|
+
"sim_time_s": float(current_payload.get("sim_time_s", 30.0) or 30.0),
|
|
66
|
+
"sampling_interval_s": float(current_payload.get("sampling_interval_s", 0.5) or 0.5),
|
|
67
|
+
"source_rate_pps": float(current_payload.get("source_rate_pps", 14.0) or 14.0),
|
|
68
|
+
"queue_weight": float(current_payload.get("queue_weight", 2.5) or 2.5),
|
|
69
|
+
"random_seed": int(current_payload.get("random_seed", 2026) or 2026),
|
|
70
|
+
"reset_target": bool(current_payload.get("reset_target", False)),
|
|
71
|
+
}
|
|
72
|
+
for key, value in defaults.items():
|
|
73
|
+
st.session_state.setdefault(_k(key), value)
|
|
74
|
+
|
|
75
|
+
c1, c2, c3, c4 = st.columns([2, 2, 1.2, 1.2])
|
|
76
|
+
with c1:
|
|
77
|
+
st.text_input("Scenario directory", key=_k("data_in"))
|
|
78
|
+
with c2:
|
|
79
|
+
st.text_input("Results directory", key=_k("data_out"))
|
|
80
|
+
with c3:
|
|
81
|
+
st.text_input("Files glob", key=_k("files"))
|
|
82
|
+
with c4:
|
|
83
|
+
st.number_input("Number of files", key=_k("nfile"), min_value=1, step=1)
|
|
84
|
+
|
|
85
|
+
c5, c6, c7 = st.columns([1.5, 1.2, 1.1])
|
|
86
|
+
with c5:
|
|
87
|
+
st.selectbox("Routing policy", options=["shortest_path", "queue_aware"], key=_k("routing_policy"))
|
|
88
|
+
with c6:
|
|
89
|
+
st.number_input("Sim time (s)", key=_k("sim_time_s"), min_value=2.0, max_value=600.0, step=1.0)
|
|
90
|
+
with c7:
|
|
91
|
+
st.checkbox("Reset output", key=_k("reset_target"))
|
|
92
|
+
|
|
93
|
+
c8, c9, c10, c11 = st.columns([1.1, 1.1, 1.1, 1.1])
|
|
94
|
+
with c8:
|
|
95
|
+
st.number_input("Sampling (s)", key=_k("sampling_interval_s"), min_value=0.1, max_value=10.0, step=0.1)
|
|
96
|
+
with c9:
|
|
97
|
+
st.number_input("Source rate (pps)", key=_k("source_rate_pps"), min_value=0.1, max_value=500.0, step=0.5)
|
|
98
|
+
with c10:
|
|
99
|
+
st.number_input("Queue weight", key=_k("queue_weight"), min_value=0.0, max_value=20.0, step=0.1)
|
|
100
|
+
with c11:
|
|
101
|
+
st.number_input("Random seed", key=_k("random_seed"), min_value=0, step=1)
|
|
102
|
+
|
|
103
|
+
candidate: dict[str, Any] = {
|
|
104
|
+
"files": (st.session_state.get(_k("files")) or "*.json").strip() or "*.json",
|
|
105
|
+
"nfile": st.session_state.get(_k("nfile"), 1),
|
|
106
|
+
"routing_policy": st.session_state.get(_k("routing_policy")) or "shortest_path",
|
|
107
|
+
"sim_time_s": st.session_state.get(_k("sim_time_s"), 30.0),
|
|
108
|
+
"sampling_interval_s": st.session_state.get(_k("sampling_interval_s"), 0.5),
|
|
109
|
+
"source_rate_pps": st.session_state.get(_k("source_rate_pps"), 14.0),
|
|
110
|
+
"queue_weight": st.session_state.get(_k("queue_weight"), 2.5),
|
|
111
|
+
"random_seed": st.session_state.get(_k("random_seed"), 2026),
|
|
112
|
+
"reset_target": bool(st.session_state.get(_k("reset_target"), False)),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
data_in_raw = (st.session_state.get(_k("data_in")) or "").strip()
|
|
116
|
+
data_out_raw = (st.session_state.get(_k("data_out")) or "").strip()
|
|
117
|
+
if data_in_raw:
|
|
118
|
+
candidate["data_in"] = data_in_raw
|
|
119
|
+
if data_out_raw:
|
|
120
|
+
candidate["data_out"] = data_out_raw
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
validated = UavRelayQueueArgs(**candidate)
|
|
124
|
+
except ValidationError as exc:
|
|
125
|
+
st.error("Invalid UAV relay queue parameters:")
|
|
126
|
+
if hasattr(env, "humanize_validation_errors"):
|
|
127
|
+
for msg in env.humanize_validation_errors(exc):
|
|
128
|
+
st.markdown(msg)
|
|
129
|
+
else:
|
|
130
|
+
st.code(str(exc))
|
|
131
|
+
else:
|
|
132
|
+
validated_payload = validated.model_dump(mode="json")
|
|
133
|
+
if validated_payload != current_payload:
|
|
134
|
+
dump_args(validated, settings_path)
|
|
135
|
+
app_settings = st.session_state.get("app_settings")
|
|
136
|
+
if not isinstance(app_settings, dict):
|
|
137
|
+
app_settings = {}
|
|
138
|
+
app_settings.setdefault("cluster", {})
|
|
139
|
+
app_settings["args"] = validated_payload
|
|
140
|
+
app_settings.setdefault("pages", {})
|
|
141
|
+
app_settings["pages"]["view_module"] = ["view_relay_resilience", "view_maps_network"]
|
|
142
|
+
st.session_state["app_settings"] = app_settings
|
|
143
|
+
st.session_state["is_args_from_ui"] = True
|
|
144
|
+
st.success(f"Saved to `{settings_path}`.")
|
|
145
|
+
else:
|
|
146
|
+
st.info("No changes to save.")
|
|
147
|
+
|
|
148
|
+
resolved_data_in = env.resolve_share_path(validated.data_in)
|
|
149
|
+
resolved_data_out = env.resolve_share_path(validated.data_out)
|
|
150
|
+
if not any(resolved_data_in.glob(validated.files)):
|
|
151
|
+
st.info("No matching scenario file exists yet. The bundled sample scenario will be seeded on first run.")
|
|
152
|
+
st.caption(
|
|
153
|
+
f"Resolved input: `{resolved_data_in}` • results: `{resolved_data_out}` • "
|
|
154
|
+
f"analysis artifacts: `{artifact_root}`"
|
|
155
|
+
)
|
|
156
|
+
st.caption(
|
|
157
|
+
"The default sample is tuned to create a queue hotspot on `relay_a` under "
|
|
158
|
+
"`shortest_path`, then improve when you switch to `queue_aware`."
|
|
159
|
+
)
|
|
160
|
+
st.caption(
|
|
161
|
+
"Each run also exports `pipeline/topology.gml`, `pipeline/allocations_steps.csv`, and "
|
|
162
|
+
"per-node trajectory CSVs so `view_maps_network` can reuse the same scenario."
|
|
163
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[args]
|
|
2
|
+
data_in = "uav_relay_queue/scenarios"
|
|
3
|
+
data_out = "uav_relay_queue/results"
|
|
4
|
+
files = "*.json"
|
|
5
|
+
nfile = 1
|
|
6
|
+
routing_policy = "shortest_path"
|
|
7
|
+
sim_time_s = 30.0
|
|
8
|
+
sampling_interval_s = 0.5
|
|
9
|
+
source_rate_pps = 14.0
|
|
10
|
+
queue_weight = 2.5
|
|
11
|
+
random_seed = 2026
|
|
12
|
+
reset_target = false
|
|
13
|
+
|
|
14
|
+
[cluster]
|
|
15
|
+
verbose = 1
|
|
16
|
+
cython = false
|
|
17
|
+
pool = false
|
|
18
|
+
rapids = false
|
|
19
|
+
cluster_enabled = false
|
|
20
|
+
scheduler = "127.0.0.1:8786"
|
|
21
|
+
workers_data_path = ""
|
|
22
|
+
|
|
23
|
+
[cluster.workers]
|
|
24
|
+
"127.0.0.1" = 2
|
|
25
|
+
|
|
26
|
+
[cluster.service_health]
|
|
27
|
+
allow_idle = false
|
|
28
|
+
max_unhealthy = 0
|
|
29
|
+
max_restart_rate = 0.25
|
|
30
|
+
|
|
31
|
+
[connector_catalog]
|
|
32
|
+
path = "connectors/data_connectors.toml"
|
|
33
|
+
|
|
34
|
+
[connector_refs]
|
|
35
|
+
scenario_sql = "uav_relay_queue_scenarios_sql"
|
|
36
|
+
operator_events = "uav_relay_queue_ops_opensearch"
|
|
37
|
+
artifact_store = "uav_relay_queue_artifact_store"
|
|
38
|
+
|
|
39
|
+
[page_connector_refs.view_relay_resilience]
|
|
40
|
+
relay_metrics = "uav_relay_queue_artifact_store"
|
|
41
|
+
operator_events = "uav_relay_queue_ops_opensearch"
|
|
42
|
+
|
|
43
|
+
[page_connector_refs.view_maps_network]
|
|
44
|
+
network_evidence = "uav_relay_queue_ops_opensearch"
|
|
45
|
+
|
|
46
|
+
[legacy_paths]
|
|
47
|
+
data_in = "uav_relay_queue/scenarios"
|
|
48
|
+
data_out = "uav_relay_queue/results"
|
|
49
|
+
|
|
50
|
+
[pages]
|
|
51
|
+
view_module = [
|
|
52
|
+
"view_relay_resilience",
|
|
53
|
+
"view_maps_network",
|
|
54
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[[connectors]]
|
|
2
|
+
id = "uav_relay_queue_scenarios_sql"
|
|
3
|
+
kind = "sql"
|
|
4
|
+
label = "UAV Relay Queue Scenarios SQL"
|
|
5
|
+
description = "Read-only relay scenario and topology metadata source."
|
|
6
|
+
uri = "postgresql://uav.example.invalid/relay_queue"
|
|
7
|
+
driver = "postgresql"
|
|
8
|
+
query_mode = "read_only"
|
|
9
|
+
|
|
10
|
+
[[connectors]]
|
|
11
|
+
id = "uav_relay_queue_ops_opensearch"
|
|
12
|
+
kind = "opensearch"
|
|
13
|
+
label = "UAV Relay Queue Operations OpenSearch"
|
|
14
|
+
description = "Relay simulation run and operator evidence index."
|
|
15
|
+
url = "https://opensearch.example.invalid"
|
|
16
|
+
index = "agilab-uav-relay-queue-*"
|
|
17
|
+
auth_ref = "env:OPENSEARCH_TOKEN"
|
|
18
|
+
|
|
19
|
+
[[connectors]]
|
|
20
|
+
id = "uav_relay_queue_artifact_store"
|
|
21
|
+
kind = "object_storage"
|
|
22
|
+
label = "UAV Relay Queue Artifact Store"
|
|
23
|
+
description = "Object-storage prefix for relay simulation artifacts."
|
|
24
|
+
provider = "s3"
|
|
25
|
+
bucket = "agilab-artifacts"
|
|
26
|
+
prefix = "uav_relay_queue/"
|
|
27
|
+
auth_ref = "env:AWS_PROFILE"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Application surface for the built-in UAV relay queue example."""
|
|
2
|
+
|
|
3
|
+
from .app_args import (
|
|
4
|
+
UavRelayQueueArgs,
|
|
5
|
+
UavRelayQueueArgsTD,
|
|
6
|
+
UavQueueArgs,
|
|
7
|
+
UavQueueArgsTD,
|
|
8
|
+
dump_args,
|
|
9
|
+
ensure_defaults,
|
|
10
|
+
load_args,
|
|
11
|
+
merge_args,
|
|
12
|
+
)
|
|
13
|
+
from .reduction import UAV_RELAY_QUEUE_REDUCE_CONTRACT
|
|
14
|
+
from .uav_relay_queue import UavRelayQueue, UavRelayQueueApp, UavQueue, UavQueueApp
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"UAV_RELAY_QUEUE_REDUCE_CONTRACT",
|
|
18
|
+
"UavRelayQueue",
|
|
19
|
+
"UavRelayQueueApp",
|
|
20
|
+
"UavRelayQueueArgs",
|
|
21
|
+
"UavRelayQueueArgsTD",
|
|
22
|
+
"UavQueue",
|
|
23
|
+
"UavQueueApp",
|
|
24
|
+
"UavQueueArgs",
|
|
25
|
+
"UavQueueArgsTD",
|
|
26
|
+
"dump_args",
|
|
27
|
+
"ensure_defaults",
|
|
28
|
+
"load_args",
|
|
29
|
+
"merge_args",
|
|
30
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Argument helpers for the built-in UAV relay queue example."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Literal, TypedDict
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
9
|
+
|
|
10
|
+
from agi_env.app_args import dump_model_to_toml, load_model_from_toml, merge_model_data
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UavRelayQueueArgs(BaseModel):
|
|
14
|
+
"""Runtime parameters for the lightweight UAV relay queue demo."""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(extra="forbid")
|
|
17
|
+
|
|
18
|
+
data_in: Path = Field(default_factory=lambda: Path("uav_relay_queue/scenarios"))
|
|
19
|
+
data_out: Path = Field(default_factory=lambda: Path("uav_relay_queue/results"))
|
|
20
|
+
files: str = "*.json"
|
|
21
|
+
nfile: int = Field(default=1, ge=1, le=50)
|
|
22
|
+
routing_policy: Literal["shortest_path", "queue_aware"] = "shortest_path"
|
|
23
|
+
sim_time_s: float = Field(default=30.0, gt=1.0, le=600.0)
|
|
24
|
+
sampling_interval_s: float = Field(default=0.5, gt=0.0, le=10.0)
|
|
25
|
+
source_rate_pps: float = Field(default=14.0, gt=0.0, le=500.0)
|
|
26
|
+
queue_weight: float = Field(default=2.5, ge=0.0, le=20.0)
|
|
27
|
+
random_seed: int = Field(default=2026, ge=0)
|
|
28
|
+
reset_target: bool = False
|
|
29
|
+
|
|
30
|
+
@model_validator(mode="after")
|
|
31
|
+
def _validate_consistency(self) -> "UavRelayQueueArgs":
|
|
32
|
+
self.files = self.files.strip() or "*.json"
|
|
33
|
+
if self.sampling_interval_s >= self.sim_time_s:
|
|
34
|
+
raise ValueError("sampling_interval_s must be smaller than sim_time_s")
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class UavRelayQueueArgsTD(TypedDict, total=False):
|
|
39
|
+
data_in: str
|
|
40
|
+
data_out: str
|
|
41
|
+
files: str
|
|
42
|
+
nfile: int
|
|
43
|
+
routing_policy: str
|
|
44
|
+
sim_time_s: float
|
|
45
|
+
sampling_interval_s: float
|
|
46
|
+
source_rate_pps: float
|
|
47
|
+
queue_weight: float
|
|
48
|
+
random_seed: int
|
|
49
|
+
reset_target: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
ArgsModel = UavRelayQueueArgs
|
|
53
|
+
ArgsOverrides = UavRelayQueueArgsTD
|
|
54
|
+
UavQueueArgs = UavRelayQueueArgs
|
|
55
|
+
UavQueueArgsTD = UavRelayQueueArgsTD
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def load_args(settings_path: str | Path, *, section: str = "args") -> UavRelayQueueArgs:
|
|
59
|
+
return load_model_from_toml(UavRelayQueueArgs, settings_path, section=section)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def merge_args(
|
|
63
|
+
base: UavRelayQueueArgs,
|
|
64
|
+
overrides: UavRelayQueueArgsTD | None = None,
|
|
65
|
+
) -> UavRelayQueueArgs:
|
|
66
|
+
return merge_model_data(base, overrides)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dump_args(
|
|
70
|
+
args: UavRelayQueueArgs,
|
|
71
|
+
settings_path: str | Path,
|
|
72
|
+
*,
|
|
73
|
+
section: str = "args",
|
|
74
|
+
create_missing: bool = True,
|
|
75
|
+
) -> None:
|
|
76
|
+
dump_model_to_toml(args, settings_path, section=section, create_missing=create_missing)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def ensure_defaults(args: UavRelayQueueArgs, **_: Any) -> UavRelayQueueArgs:
|
|
80
|
+
return args
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
"ArgsModel",
|
|
85
|
+
"ArgsOverrides",
|
|
86
|
+
"UavRelayQueueArgs",
|
|
87
|
+
"UavRelayQueueArgsTD",
|
|
88
|
+
"UavQueueArgs",
|
|
89
|
+
"UavQueueArgsTD",
|
|
90
|
+
"dump_args",
|
|
91
|
+
"ensure_defaults",
|
|
92
|
+
"load_args",
|
|
93
|
+
"merge_args",
|
|
94
|
+
]
|