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.
Files changed (28) hide show
  1. agi_app_uav_relay_queue/__init__.py +33 -0
  2. agi_app_uav_relay_queue/project/uav_relay_queue_project/README.md +64 -0
  3. agi_app_uav_relay_queue/project/uav_relay_queue_project/lab_stages.toml +4 -0
  4. agi_app_uav_relay_queue/project/uav_relay_queue_project/notebook_export.toml +26 -0
  5. agi_app_uav_relay_queue/project/uav_relay_queue_project/pipeline_view.dot +13 -0
  6. agi_app_uav_relay_queue/project/uav_relay_queue_project/pyproject.toml +13 -0
  7. agi_app_uav_relay_queue/project/uav_relay_queue_project/service_templates/train_then_serve_policy_run.json +65 -0
  8. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/app_args_form.py +163 -0
  9. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/app_settings.toml +54 -0
  10. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/connectors/data_connectors.toml +27 -0
  11. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/pre_prompt.json +1 -0
  12. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/__init__.py +30 -0
  13. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/app_args.py +94 -0
  14. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/reduction.py +204 -0
  15. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/sample_data/uav_queue_hotspot.json +50 -0
  16. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/uav_relay_queue.py +143 -0
  17. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/uav_relay_queue_alias.py +5 -0
  18. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue/uav_relay_queue_args.py +23 -0
  19. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue_worker/__init__.py +5 -0
  20. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue_worker/pyproject.toml +13 -0
  21. agi_app_uav_relay_queue/project/uav_relay_queue_project/src/uav_relay_queue_worker/uav_relay_queue_worker.py +713 -0
  22. agi_app_uav_relay_queue/project/uav_relay_queue_project/uv_config.toml +5 -0
  23. agi_app_uav_relay_queue-0.1.0.dist-info/METADATA +49 -0
  24. agi_app_uav_relay_queue-0.1.0.dist-info/RECORD +28 -0
  25. agi_app_uav_relay_queue-0.1.0.dist-info/WHEEL +5 -0
  26. agi_app_uav_relay_queue-0.1.0.dist-info/entry_points.txt +3 -0
  27. agi_app_uav_relay_queue-0.1.0.dist-info/licenses/LICENSE +22 -0
  28. 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,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
+ ]