Commit Β·
3e4ef62
1
Parent(s): e23f30b
Final infrastructure and documentation fix for Phase 2 validation
Browse files- README.md +41 -24
- check_graders.py +77 -0
- core/__init__.py +11 -2
- core/tasks.py +3 -3
- data/memory.json +0 -0
- graders/__init__.py +11 -0
- graders/easy.py +40 -0
- graders/hard.py +40 -0
- core/graders.py β graders/medium.py +3 -32
- inference.py +1 -1
- models.py +2 -2
- openenv.yaml +3 -3
- pyproject.toml +2 -2
- server/app.py +9 -9
- server/grid_world_environment.py +6 -6
- server/static/index.html +3 -3
- server/static/script.js +6 -6
- tests/test_env.py +10 -10
README.md
CHANGED
|
@@ -82,9 +82,12 @@ The codebase follows a clean separation-of-concerns architecture across four dis
|
|
| 82 |
|
| 83 |
```
|
| 84 |
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
βββ core/ # Simulation Logic Layer
|
| 86 |
β βββ drone.py # Movement physics & battery drain
|
| 87 |
-
β βββ graders.py # Unified scoring logic (0.01 - 0.99)
|
| 88 |
β βββ grid_generator.py # Map generation logic
|
| 89 |
β βββ obstacles.py # Collision & terrain detection
|
| 90 |
β βββ state_manager.py # Episodic state management
|
|
@@ -471,7 +474,7 @@ MODEL_NAME=Qwen/Qwen2.5-72B-Instruct python inference.py
|
|
| 471 |
The runner emits structured benchmark-compatible log lines:
|
| 472 |
|
| 473 |
```
|
| 474 |
-
[START] task=drone_env.
|
| 475 |
[STEP] step=1 action=RIGHT reward=0.10 done=false error=null
|
| 476 |
[STEP] step=2 action=RIGHT reward=0.10 done=false error=null
|
| 477 |
[STEP] step=3 action=RIGHT reward=0.10 done=false error=null
|
|
@@ -568,15 +571,15 @@ app: drone_env.server.app:app
|
|
| 568 |
port: 8000
|
| 569 |
tasks:
|
| 570 |
- id: easy_delivery
|
| 571 |
-
grader: drone_env.
|
| 572 |
- id: medium_delivery
|
| 573 |
-
grader: drone_env.
|
| 574 |
- id: hard_delivery
|
| 575 |
-
grader: drone_env.
|
| 576 |
graders:
|
| 577 |
-
- id: drone_env.
|
| 578 |
-
- id: drone_env.
|
| 579 |
-
- id: drone_env.
|
| 580 |
```
|
| 581 |
|
| 582 |
### Validate Before Submission
|
|
@@ -611,8 +614,20 @@ docker build .
|
|
| 611 |
openenv validate
|
| 612 |
```
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
A passing run produces:
|
| 615 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
========================================
|
| 617 |
All 3/3 checks passed!
|
| 618 |
Your submission is ready to submit.
|
|
@@ -712,7 +727,7 @@ $$r_{\text{shaping}} = (d_{\text{before}} - d_{\text{after}}) \times 0.05$$
|
|
| 712 |
|
| 713 |
## Grading & Evaluation
|
| 714 |
|
| 715 |
-
Scores are computed by `
|
| 716 |
|
| 717 |
$$\text{score} = 0.8 \times \underbrace{\frac{\text{deliveries done}}{\text{deliveries total}}}_{\text{delivery ratio}} + 0.2 \times \underbrace{\left( 0.5 \cdot \text{battery} + 0.5 \cdot \left(1 - \frac{\text{steps}}{\text{max steps}}\right) \right)}_{\text{efficiency}}$$
|
| 718 |
|
|
@@ -774,19 +789,21 @@ The **SkyRelic** ecosystem is divided into four primary layers, interconnected v
|
|
| 774 |
|
| 775 |
```
|
| 776 |
drone_env/
|
| 777 |
-
βββ
|
| 778 |
-
β βββ
|
| 779 |
-
β βββ
|
| 780 |
-
β βββ
|
| 781 |
-
β
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
βββ
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
βββ
|
| 789 |
-
|
|
|
|
|
|
|
| 790 |
β βββ grid_world_environment.py # DroneDeliveryEnvironment (OpenEnv base)
|
| 791 |
β βββ Dockerfile # Multi-stage production image
|
| 792 |
β βββ static/ # Browser dashboard (HTML/JS/CSS)
|
|
@@ -887,8 +904,8 @@ Build system uses [Meta's BSD-licensed](https://opensource.org/licenses/BSD-3-Cl
|
|
| 887 |
The **SkyRelic** environment has been updated to fully comply with the **Meta PyTorch Hackathon Phase 2 Deep Validation** requirements.
|
| 888 |
|
| 889 |
### π‘οΈ Validation Fixes
|
| 890 |
-
- **Strict Score Clamping**: All mission scores and rewards are now strictly clamped to the **(0.01, 0.99)** range in `
|
| 891 |
-
- **Full Identity Sync (Grader Discovery)**: Task and grader identifiers have been synchronized across the manifest (`openenv.yaml`), backend API, and simulation core using full Python module paths (e.g., `drone_env.
|
| 892 |
- **Differentiated Reward Scalars**: To provide clearer learning signals, reward scalars for step, wait, and collision penalties have been updated to difficulty-specific tiers:
|
| 893 |
- **Easy Mission**: 0.10 (10%)
|
| 894 |
- **Medium Mission**: 0.15 (15%)
|
|
|
|
| 82 |
|
| 83 |
```
|
| 84 |
.
|
| 85 |
+
βββ graders/ # Unified Graders Package (Root)
|
| 86 |
+
β βββ easy.py # Easy task scoring logic
|
| 87 |
+
β βββ medium.py # Medium task scoring logic
|
| 88 |
+
β βββ hard.py # Hard task scoring logic
|
| 89 |
βββ core/ # Simulation Logic Layer
|
| 90 |
β βββ drone.py # Movement physics & battery drain
|
|
|
|
| 91 |
β βββ grid_generator.py # Map generation logic
|
| 92 |
β βββ obstacles.py # Collision & terrain detection
|
| 93 |
β βββ state_manager.py # Episodic state management
|
|
|
|
| 474 |
The runner emits structured benchmark-compatible log lines:
|
| 475 |
|
| 476 |
```
|
| 477 |
+
[START] task=drone_env.graders:grade_easy env=drone_env_v1 model=Qwen/Qwen2.5-7B-Instruct
|
| 478 |
[STEP] step=1 action=RIGHT reward=0.10 done=false error=null
|
| 479 |
[STEP] step=2 action=RIGHT reward=0.10 done=false error=null
|
| 480 |
[STEP] step=3 action=RIGHT reward=0.10 done=false error=null
|
|
|
|
| 571 |
port: 8000
|
| 572 |
tasks:
|
| 573 |
- id: easy_delivery
|
| 574 |
+
grader: drone_env.graders:grade_easy
|
| 575 |
- id: medium_delivery
|
| 576 |
+
grader: drone_env.graders:grade_medium
|
| 577 |
- id: hard_delivery
|
| 578 |
+
grader: drone_env.graders:grade_hard
|
| 579 |
graders:
|
| 580 |
+
- id: drone_env.graders:grade_easy
|
| 581 |
+
- id: drone_env.graders:grade_medium
|
| 582 |
+
- id: drone_env.graders:grade_hard
|
| 583 |
```
|
| 584 |
|
| 585 |
### Validate Before Submission
|
|
|
|
| 614 |
openenv validate
|
| 615 |
```
|
| 616 |
|
| 617 |
+
#### Local Grader Check
|
| 618 |
+
To verify that all 3 tasks have valid, resolvable graders before pushing:
|
| 619 |
+
```bash
|
| 620 |
+
python check_graders.py
|
| 621 |
+
```
|
| 622 |
+
|
| 623 |
A passing run produces:
|
| 624 |
```
|
| 625 |
+
========================================
|
| 626 |
+
Summary: 3 valid graders found.
|
| 627 |
+
π LOCAL CHECK PASSED.
|
| 628 |
+
========================================
|
| 629 |
+
```
|
| 630 |
+
|
| 631 |
========================================
|
| 632 |
All 3/3 checks passed!
|
| 633 |
Your submission is ready to submit.
|
|
|
|
| 727 |
|
| 728 |
## Grading & Evaluation
|
| 729 |
|
| 730 |
+
Scores are computed by the `graders/` package using a unified formula:
|
| 731 |
|
| 732 |
$$\text{score} = 0.8 \times \underbrace{\frac{\text{deliveries done}}{\text{deliveries total}}}_{\text{delivery ratio}} + 0.2 \times \underbrace{\left( 0.5 \cdot \text{battery} + 0.5 \cdot \left(1 - \frac{\text{steps}}{\text{max steps}}\right) \right)}_{\text{efficiency}}$$
|
| 733 |
|
|
|
|
| 789 |
|
| 790 |
```
|
| 791 |
drone_env/
|
| 792 |
+
βββ graders/ # Unified grader package
|
| 793 |
+
β βββ __init__.py # GRADERS lookup dictionary
|
| 794 |
+
β βββ easy.py # Easy task logic
|
| 795 |
+
β βββ medium.py # Medium task logic
|
| 796 |
+
β βββ hard.py # Hard task logic
|
| 797 |
+
βββ core/ # Simulation physics & tasks
|
| 798 |
+
βββ rl/ # DQN & Neural Training
|
| 799 |
+
βββ server/ # FastAPI & Dashboard
|
| 800 |
+
βββ data/ # Persistence (Memory & Logs)
|
| 801 |
+
βββ tests/ # Unit & API tests
|
| 802 |
+
βββ check_graders.py # Local grader validation script
|
| 803 |
+
βββ openenv.yaml # Mission Manifest
|
| 804 |
+
βββ pyproject.toml # Package configuration
|
| 805 |
+
βββ validate-submission.sh # Submission validator
|
| 806 |
+
```
|
| 807 |
β βββ grid_world_environment.py # DroneDeliveryEnvironment (OpenEnv base)
|
| 808 |
β βββ Dockerfile # Multi-stage production image
|
| 809 |
β βββ static/ # Browser dashboard (HTML/JS/CSS)
|
|
|
|
| 904 |
The **SkyRelic** environment has been updated to fully comply with the **Meta PyTorch Hackathon Phase 2 Deep Validation** requirements.
|
| 905 |
|
| 906 |
### π‘οΈ Validation Fixes
|
| 907 |
+
- **Strict Score Clamping**: All mission scores and rewards are now strictly clamped to the **(0.01, 0.99)** range in the `graders/` package and `server/grid_world_environment.py`. This prevents the "out of range" (exactly 0.0 or 1.0) failures reported by the automated validator.
|
| 908 |
+
- **Full Identity Sync (Grader Discovery)**: Task and grader identifiers have been synchronized across the manifest (`openenv.yaml`), backend API, and simulation core using full Python module paths (e.g., `drone_env.graders:grade_easy`). This ensures the Meta validator can successfully discover and import the grading functions.
|
| 909 |
- **Differentiated Reward Scalars**: To provide clearer learning signals, reward scalars for step, wait, and collision penalties have been updated to difficulty-specific tiers:
|
| 910 |
- **Easy Mission**: 0.10 (10%)
|
| 911 |
- **Medium Mission**: 0.15 (15%)
|
check_graders.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import yaml
|
| 2 |
+
import importlib
|
| 3 |
+
import sys
|
| 4 |
+
import os
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
def check_graders():
|
| 8 |
+
# Ensure current directory is in path
|
| 9 |
+
sys.path.insert(0, os.getcwd())
|
| 10 |
+
|
| 11 |
+
yaml_path = Path("openenv.yaml")
|
| 12 |
+
if not yaml_path.exists():
|
| 13 |
+
print("β Error: openenv.yaml not found!")
|
| 14 |
+
return
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
with open(yaml_path, 'r') as f:
|
| 18 |
+
spec = yaml.safe_load(f)
|
| 19 |
+
except Exception as e:
|
| 20 |
+
print(f"β Error parsing openenv.yaml: {e}")
|
| 21 |
+
return
|
| 22 |
+
|
| 23 |
+
tasks = spec.get("tasks", [])
|
| 24 |
+
print(f"π Found {len(tasks)} tasks in openenv.yaml\n")
|
| 25 |
+
|
| 26 |
+
valid_count = 0
|
| 27 |
+
|
| 28 |
+
for task in tasks:
|
| 29 |
+
task_id = task.get("id")
|
| 30 |
+
grader_str = task.get("grader")
|
| 31 |
+
|
| 32 |
+
print(f"--- Task: {task_id} ---")
|
| 33 |
+
if not grader_str:
|
| 34 |
+
print(" β No grader field defined.")
|
| 35 |
+
continue
|
| 36 |
+
|
| 37 |
+
print(f" Attempting to resolve: {grader_str}")
|
| 38 |
+
try:
|
| 39 |
+
if ":" not in grader_str:
|
| 40 |
+
print(f" β Invalid format. Expected 'module:function'")
|
| 41 |
+
continue
|
| 42 |
+
|
| 43 |
+
module_path, func_name = grader_str.split(":")
|
| 44 |
+
|
| 45 |
+
module = None
|
| 46 |
+
# Try full path first (as it will be in the platform)
|
| 47 |
+
try:
|
| 48 |
+
module = importlib.import_module(module_path)
|
| 49 |
+
except ImportError:
|
| 50 |
+
# Local fallback: try without 'drone_env.' prefix if we're in the project root
|
| 51 |
+
if module_path.startswith("drone_env."):
|
| 52 |
+
alt_path = module_path.replace("drone_env.", "", 1)
|
| 53 |
+
try:
|
| 54 |
+
module = importlib.import_module(alt_path)
|
| 55 |
+
except ImportError as eb:
|
| 56 |
+
print(f" β Module search failed (tried {module_path} and {alt_path}): {eb}")
|
| 57 |
+
|
| 58 |
+
if module:
|
| 59 |
+
func = getattr(module, func_name, None)
|
| 60 |
+
if func and callable(func):
|
| 61 |
+
print(f" β
SUCCESS: Found {func_name} in {module.__name__}")
|
| 62 |
+
valid_count += 1
|
| 63 |
+
else:
|
| 64 |
+
print(f" β {func_name} not found or not callable in {module.__name__}")
|
| 65 |
+
else:
|
| 66 |
+
pass # Already printed error
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f" β Error: {e}")
|
| 69 |
+
|
| 70 |
+
print(f"\nSummary: {valid_count} valid graders found.")
|
| 71 |
+
if valid_count >= 3:
|
| 72 |
+
print("π LOCAL CHECK PASSED.")
|
| 73 |
+
else:
|
| 74 |
+
print("π¨ LOCAL CHECK FAILED.")
|
| 75 |
+
|
| 76 |
+
if __name__ == "__main__":
|
| 77 |
+
check_graders()
|
core/__init__.py
CHANGED
|
@@ -1,4 +1,13 @@
|
|
| 1 |
-
|
| 2 |
-
from .graders import grade_easy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
__all__ = ["grade_easy", "grade_medium", "grade_hard"]
|
|
|
|
| 1 |
+
def grade_easy(state):
|
| 2 |
+
from drone_env.graders.easy import grade_easy as fn
|
| 3 |
+
return fn(state)
|
| 4 |
+
|
| 5 |
+
def grade_medium(state):
|
| 6 |
+
from drone_env.graders.medium import grade_medium as fn
|
| 7 |
+
return fn(state)
|
| 8 |
+
|
| 9 |
+
def grade_hard(state):
|
| 10 |
+
from drone_env.graders.hard import grade_hard as fn
|
| 11 |
+
return fn(state)
|
| 12 |
|
| 13 |
__all__ = ["grade_easy", "grade_medium", "grade_hard"]
|
core/tasks.py
CHANGED
|
@@ -4,7 +4,7 @@ Mission configurations for Drone Delivery missions.
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
TASK_CONFIG = {
|
| 7 |
-
"drone_env.
|
| 8 |
"width": 10,
|
| 9 |
"height": 10,
|
| 10 |
"n_buildings": 4,
|
|
@@ -24,7 +24,7 @@ TASK_CONFIG = {
|
|
| 24 |
"r_wall": 0.10,
|
| 25 |
"r_blocked": 0.10,
|
| 26 |
},
|
| 27 |
-
"drone_env.
|
| 28 |
"width": 14,
|
| 29 |
"height": 14,
|
| 30 |
"n_buildings": 8,
|
|
@@ -44,7 +44,7 @@ TASK_CONFIG = {
|
|
| 44 |
"r_wall": 0.15,
|
| 45 |
"r_blocked": 0.15,
|
| 46 |
},
|
| 47 |
-
"drone_env.
|
| 48 |
"width": 18,
|
| 49 |
"height": 18,
|
| 50 |
"n_buildings": 12,
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
TASK_CONFIG = {
|
| 7 |
+
"drone_env.graders.easy:grade_easy": {
|
| 8 |
"width": 10,
|
| 9 |
"height": 10,
|
| 10 |
"n_buildings": 4,
|
|
|
|
| 24 |
"r_wall": 0.10,
|
| 25 |
"r_blocked": 0.10,
|
| 26 |
},
|
| 27 |
+
"drone_env.graders.medium:grade_medium": {
|
| 28 |
"width": 14,
|
| 29 |
"height": 14,
|
| 30 |
"n_buildings": 8,
|
|
|
|
| 44 |
"r_wall": 0.15,
|
| 45 |
"r_blocked": 0.15,
|
| 46 |
},
|
| 47 |
+
"drone_env.graders.hard:grade_hard": {
|
| 48 |
"width": 18,
|
| 49 |
"height": 18,
|
| 50 |
"n_buildings": 12,
|
data/memory.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
graders/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .easy import grade_easy
|
| 2 |
+
from .medium import grade_medium
|
| 3 |
+
from .hard import grade_hard
|
| 4 |
+
|
| 5 |
+
GRADERS = {
|
| 6 |
+
"drone_env.graders.easy:grade_easy": grade_easy,
|
| 7 |
+
"drone_env.graders.medium:grade_medium": grade_medium,
|
| 8 |
+
"drone_env.graders.hard:grade_hard": grade_hard,
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
__all__ = ["grade_easy", "grade_medium", "grade_hard", "GRADERS"]
|
graders/easy.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluation logic for easy_delivery task.
|
| 3 |
+
This file is self-contained.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
def _get_attr(state, key, default=0):
|
| 7 |
+
"""Safely get an attribute from a state object or dict."""
|
| 8 |
+
if isinstance(state, dict):
|
| 9 |
+
return state.get(key, default)
|
| 10 |
+
return getattr(state, key, default)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def compute_grade(state, max_steps: float) -> float:
|
| 14 |
+
"""
|
| 15 |
+
Unified grade calculation:
|
| 16 |
+
- 80% weighted by deliveries completed.
|
| 17 |
+
- 20% weighted by efficiency (remaining battery and steps).
|
| 18 |
+
"""
|
| 19 |
+
deliveries_total = _get_attr(state, "deliveries_total", 0)
|
| 20 |
+
deliveries_done = _get_attr(state, "deliveries_done", 0)
|
| 21 |
+
battery = _get_attr(state, "battery", 1.0)
|
| 22 |
+
step_count = _get_attr(state, "step_count", 0)
|
| 23 |
+
|
| 24 |
+
if deliveries_total == 0:
|
| 25 |
+
return 0.5
|
| 26 |
+
|
| 27 |
+
delivery_ratio = deliveries_done / deliveries_total
|
| 28 |
+
efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
|
| 29 |
+
efficiency = max(0.0, min(1.0, efficiency))
|
| 30 |
+
|
| 31 |
+
score = (delivery_ratio * 0.8) + (efficiency * 0.2)
|
| 32 |
+
if deliveries_done < deliveries_total:
|
| 33 |
+
score = min(score, 0.49)
|
| 34 |
+
|
| 35 |
+
return max(0.01, min(0.99, float(score)))
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def grade_easy(state) -> float:
|
| 39 |
+
"""Grader for easy_delivery task (10x10 grid, 1 delivery, 60 max steps)."""
|
| 40 |
+
return compute_grade(state, 60.0)
|
graders/hard.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluation logic for hard_delivery task.
|
| 3 |
+
This file is self-contained.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
def _get_attr(state, key, default=0):
|
| 7 |
+
"""Safely get an attribute from a state object or dict."""
|
| 8 |
+
if isinstance(state, dict):
|
| 9 |
+
return state.get(key, default)
|
| 10 |
+
return getattr(state, key, default)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def compute_grade(state, max_steps: float) -> float:
|
| 14 |
+
"""
|
| 15 |
+
Unified grade calculation:
|
| 16 |
+
- 80% weighted by deliveries completed.
|
| 17 |
+
- 20% weighted by efficiency (remaining battery and steps).
|
| 18 |
+
"""
|
| 19 |
+
deliveries_total = _get_attr(state, "deliveries_total", 0)
|
| 20 |
+
deliveries_done = _get_attr(state, "deliveries_done", 0)
|
| 21 |
+
battery = _get_attr(state, "battery", 1.0)
|
| 22 |
+
step_count = _get_attr(state, "step_count", 0)
|
| 23 |
+
|
| 24 |
+
if deliveries_total == 0:
|
| 25 |
+
return 0.5
|
| 26 |
+
|
| 27 |
+
delivery_ratio = deliveries_done / deliveries_total
|
| 28 |
+
efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
|
| 29 |
+
efficiency = max(0.0, min(1.0, efficiency))
|
| 30 |
+
|
| 31 |
+
score = (delivery_ratio * 0.8) + (efficiency * 0.2)
|
| 32 |
+
if deliveries_done < deliveries_total:
|
| 33 |
+
score = min(score, 0.49)
|
| 34 |
+
|
| 35 |
+
return max(0.01, min(0.99, float(score)))
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def grade_hard(state) -> float:
|
| 39 |
+
"""Grader for hard_delivery task (18x18 grid, 5 deliveries, 160 max steps)."""
|
| 40 |
+
return compute_grade(state, 160.0)
|
core/graders.py β graders/medium.py
RENAMED
|
@@ -1,14 +1,8 @@
|
|
| 1 |
"""
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
NOTE: This module is intentionally self-contained with NO external package imports.
|
| 6 |
-
The OpenEnv validator imports grader functions directly via the module path
|
| 7 |
-
(e.g. drone_env.core.graders:grade_easy). Any import failure here means the
|
| 8 |
-
validator counts 0 working graders β causing "Not enough tasks with graders" error.
|
| 9 |
"""
|
| 10 |
|
| 11 |
-
|
| 12 |
def _get_attr(state, key, default=0):
|
| 13 |
"""Safely get an attribute from a state object or dict."""
|
| 14 |
if isinstance(state, dict):
|
|
@@ -21,7 +15,6 @@ def compute_grade(state, max_steps: float) -> float:
|
|
| 21 |
Unified grade calculation:
|
| 22 |
- 80% weighted by deliveries completed.
|
| 23 |
- 20% weighted by efficiency (remaining battery and steps).
|
| 24 |
-
Accepts any object or dict with the required fields.
|
| 25 |
"""
|
| 26 |
deliveries_total = _get_attr(state, "deliveries_total", 0)
|
| 27 |
deliveries_done = _get_attr(state, "deliveries_done", 0)
|
|
@@ -29,41 +22,19 @@ def compute_grade(state, max_steps: float) -> float:
|
|
| 29 |
step_count = _get_attr(state, "step_count", 0)
|
| 30 |
|
| 31 |
if deliveries_total == 0:
|
| 32 |
-
return 0.5
|
| 33 |
|
| 34 |
delivery_ratio = deliveries_done / deliveries_total
|
| 35 |
-
|
| 36 |
-
# Efficiency factor (0.0 to 1.0)
|
| 37 |
efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
|
| 38 |
efficiency = max(0.0, min(1.0, efficiency))
|
| 39 |
|
| 40 |
score = (delivery_ratio * 0.8) + (efficiency * 0.2)
|
| 41 |
-
|
| 42 |
-
# Cap score if not all deliveries are done
|
| 43 |
if deliveries_done < deliveries_total:
|
| 44 |
score = min(score, 0.49)
|
| 45 |
|
| 46 |
-
# Hackathon Requirement: Score must be strictly between 0.0 and 1.0
|
| 47 |
return max(0.01, min(0.99, float(score)))
|
| 48 |
|
| 49 |
|
| 50 |
-
def grade_easy(state) -> float:
|
| 51 |
-
"""Grader for easy_delivery task (10x10 grid, 1 delivery, 60 max steps)."""
|
| 52 |
-
return compute_grade(state, 60.0)
|
| 53 |
-
|
| 54 |
-
|
| 55 |
def grade_medium(state) -> float:
|
| 56 |
"""Grader for medium_delivery task (14x14 grid, 3 deliveries, 100 max steps)."""
|
| 57 |
return compute_grade(state, 100.0)
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
def grade_hard(state) -> float:
|
| 61 |
-
"""Grader for hard_delivery task (18x18 grid, 5 deliveries, 160 max steps)."""
|
| 62 |
-
return compute_grade(state, 160.0)
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
GRADERS = {
|
| 66 |
-
"drone_env.core.graders:grade_easy": grade_easy,
|
| 67 |
-
"drone_env.core.graders:grade_medium": grade_medium,
|
| 68 |
-
"drone_env.core.graders:grade_hard": grade_hard,
|
| 69 |
-
}
|
|
|
|
| 1 |
"""
|
| 2 |
+
Evaluation logic for medium_delivery task.
|
| 3 |
+
This file is self-contained.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
|
|
|
| 6 |
def _get_attr(state, key, default=0):
|
| 7 |
"""Safely get an attribute from a state object or dict."""
|
| 8 |
if isinstance(state, dict):
|
|
|
|
| 15 |
Unified grade calculation:
|
| 16 |
- 80% weighted by deliveries completed.
|
| 17 |
- 20% weighted by efficiency (remaining battery and steps).
|
|
|
|
| 18 |
"""
|
| 19 |
deliveries_total = _get_attr(state, "deliveries_total", 0)
|
| 20 |
deliveries_done = _get_attr(state, "deliveries_done", 0)
|
|
|
|
| 22 |
step_count = _get_attr(state, "step_count", 0)
|
| 23 |
|
| 24 |
if deliveries_total == 0:
|
| 25 |
+
return 0.5
|
| 26 |
|
| 27 |
delivery_ratio = deliveries_done / deliveries_total
|
|
|
|
|
|
|
| 28 |
efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
|
| 29 |
efficiency = max(0.0, min(1.0, efficiency))
|
| 30 |
|
| 31 |
score = (delivery_ratio * 0.8) + (efficiency * 0.2)
|
|
|
|
|
|
|
| 32 |
if deliveries_done < deliveries_total:
|
| 33 |
score = min(score, 0.49)
|
| 34 |
|
|
|
|
| 35 |
return max(0.01, min(0.99, float(score)))
|
| 36 |
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
def grade_medium(state) -> float:
|
| 39 |
"""Grader for medium_delivery task (14x14 grid, 3 deliveries, 100 max steps)."""
|
| 40 |
return compute_grade(state, 100.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inference.py
CHANGED
|
@@ -44,7 +44,7 @@ API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY") or os.getenv("OPENAI_API
|
|
| 44 |
API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
|
| 45 |
MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-7B-Instruct"
|
| 46 |
|
| 47 |
-
TASK_NAME = os.getenv("
|
| 48 |
BENCHMARK = os.getenv("DRONE_BENCHMARK", "drone_env_v1")
|
| 49 |
|
| 50 |
MAX_STEPS = 60
|
|
|
|
| 44 |
API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
|
| 45 |
MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-7B-Instruct"
|
| 46 |
|
| 47 |
+
TASK_NAME = os.getenv("TASK_NAME", "drone_env.graders.easy:grade_easy")
|
| 48 |
BENCHMARK = os.getenv("DRONE_BENCHMARK", "drone_env_v1")
|
| 49 |
|
| 50 |
MAX_STEPS = 60
|
models.py
CHANGED
|
@@ -14,7 +14,7 @@ class DroneAction(BaseModel):
|
|
| 14 |
)
|
| 15 |
task_name: Optional[str] = Field(
|
| 16 |
default=None,
|
| 17 |
-
description="Task to load on reset: drone_env.
|
| 18 |
)
|
| 19 |
|
| 20 |
|
|
@@ -43,7 +43,7 @@ class DroneObservation(BaseModel):
|
|
| 43 |
|
| 44 |
class DroneState(BaseModel):
|
| 45 |
episode_id: str = ""
|
| 46 |
-
task_name: str = "drone_env.
|
| 47 |
step_count: int = 0
|
| 48 |
done: bool = False
|
| 49 |
reward_total: float = 0.0
|
|
|
|
| 14 |
)
|
| 15 |
task_name: Optional[str] = Field(
|
| 16 |
default=None,
|
| 17 |
+
description="Task to load on reset: drone_env.graders.easy:grade_easy | medium | hard",
|
| 18 |
)
|
| 19 |
|
| 20 |
|
|
|
|
| 43 |
|
| 44 |
class DroneState(BaseModel):
|
| 45 |
episode_id: str = ""
|
| 46 |
+
task_name: str = "drone_env.graders.easy:grade_easy"
|
| 47 |
step_count: int = 0
|
| 48 |
done: bool = False
|
| 49 |
reward_total: float = 0.0
|
openenv.yaml
CHANGED
|
@@ -6,8 +6,8 @@ app: server.app:app
|
|
| 6 |
port: 8000
|
| 7 |
tasks:
|
| 8 |
- id: easy_delivery
|
| 9 |
-
grader: drone_env.
|
| 10 |
- id: medium_delivery
|
| 11 |
-
grader: drone_env.
|
| 12 |
- id: hard_delivery
|
| 13 |
-
grader: drone_env.
|
|
|
|
| 6 |
port: 8000
|
| 7 |
tasks:
|
| 8 |
- id: easy_delivery
|
| 9 |
+
grader: drone_env.graders.easy:grade_easy
|
| 10 |
- id: medium_delivery
|
| 11 |
+
grader: drone_env.graders.medium:grade_medium
|
| 12 |
- id: hard_delivery
|
| 13 |
+
grader: drone_env.graders.hard:grade_hard
|
pyproject.toml
CHANGED
|
@@ -35,5 +35,5 @@ server = "drone_env.server.app:main"
|
|
| 35 |
|
| 36 |
[tool.setuptools]
|
| 37 |
include-package-data = true
|
| 38 |
-
packages = ["drone_env", "drone_env.server", "drone_env.core", "drone_env.rl"]
|
| 39 |
-
package-dir = { "drone_env" = ".", "drone_env.server" = "server", "drone_env.core" = "core", "drone_env.rl" = "rl" }
|
|
|
|
| 35 |
|
| 36 |
[tool.setuptools]
|
| 37 |
include-package-data = true
|
| 38 |
+
packages = ["drone_env", "drone_env.server", "drone_env.graders", "drone_env.core", "drone_env.rl"]
|
| 39 |
+
package-dir = { "drone_env" = ".", "drone_env.server" = "server", "drone_env.graders" = "graders", "drone_env.core" = "core", "drone_env.rl" = "rl" }
|
server/app.py
CHANGED
|
@@ -25,7 +25,7 @@ if str(BASE_DIR) not in sys.path:
|
|
| 25 |
from drone_env.models import DroneAction, DroneObservation, DroneState
|
| 26 |
from drone_env.server.grid_world_environment import DroneDeliveryEnvironment
|
| 27 |
from drone_env.core.tasks import TASK_CONFIG
|
| 28 |
-
from drone_env.
|
| 29 |
from drone_env.rl.trainer import PathLearner, get_action_from_policy
|
| 30 |
|
| 31 |
app = FastAPI(
|
|
@@ -88,9 +88,9 @@ async def metadata():
|
|
| 88 |
"description": "SkyRelic Drone Delivery reinforcement-learning environment with easy/medium/hard delivery tasks on a grid world.",
|
| 89 |
"version": "0.2.1",
|
| 90 |
"tasks": [
|
| 91 |
-
{"id": "easy_delivery", "grader": "drone_env.
|
| 92 |
-
{"id": "medium_delivery", "grader": "drone_env.
|
| 93 |
-
{"id": "hard_delivery", "grader": "drone_env.
|
| 94 |
],
|
| 95 |
}
|
| 96 |
|
|
@@ -144,9 +144,9 @@ async def get_state():
|
|
| 144 |
|
| 145 |
# Task ID β grader key mapping (matches openenv.yaml task ids)
|
| 146 |
TASK_ID_TO_GRADER = {
|
| 147 |
-
"easy_delivery": "drone_env.
|
| 148 |
-
"medium_delivery": "drone_env.
|
| 149 |
-
"hard_delivery": "drone_env.
|
| 150 |
}
|
| 151 |
|
| 152 |
@app.get("/grade/{task_name}")
|
|
@@ -207,7 +207,7 @@ async def get_terminal_logs():
|
|
| 207 |
@app.get("/rewards")
|
| 208 |
async def get_rewards():
|
| 209 |
"""Return the reward configuration for the current task."""
|
| 210 |
-
task_name = _env.state.task_name or "drone_env.
|
| 211 |
config = TASK_CONFIG.get(task_name, {})
|
| 212 |
# Filter only reward keys
|
| 213 |
rewards = {k: v for k, v in config.items() if k.startswith("r_")}
|
|
@@ -248,7 +248,7 @@ async def get_memory_logs():
|
|
| 248 |
|
| 249 |
@app.post("/predict")
|
| 250 |
async def predict(obs: DroneObservation):
|
| 251 |
-
task_name = _env.state.task_name or "drone_env.
|
| 252 |
action_str = get_action_from_policy(obs, task_name)
|
| 253 |
return {"direction": action_str}
|
| 254 |
|
|
|
|
| 25 |
from drone_env.models import DroneAction, DroneObservation, DroneState
|
| 26 |
from drone_env.server.grid_world_environment import DroneDeliveryEnvironment
|
| 27 |
from drone_env.core.tasks import TASK_CONFIG
|
| 28 |
+
from drone_env.graders import GRADERS
|
| 29 |
from drone_env.rl.trainer import PathLearner, get_action_from_policy
|
| 30 |
|
| 31 |
app = FastAPI(
|
|
|
|
| 88 |
"description": "SkyRelic Drone Delivery reinforcement-learning environment with easy/medium/hard delivery tasks on a grid world.",
|
| 89 |
"version": "0.2.1",
|
| 90 |
"tasks": [
|
| 91 |
+
{"id": "easy_delivery", "grader": "drone_env.graders.easy:grade_easy"},
|
| 92 |
+
{"id": "medium_delivery", "grader": "drone_env.graders.medium:grade_medium"},
|
| 93 |
+
{"id": "hard_delivery", "grader": "drone_env.graders.hard:grade_hard"},
|
| 94 |
],
|
| 95 |
}
|
| 96 |
|
|
|
|
| 144 |
|
| 145 |
# Task ID β grader key mapping (matches openenv.yaml task ids)
|
| 146 |
TASK_ID_TO_GRADER = {
|
| 147 |
+
"easy_delivery": "drone_env.graders.easy:grade_easy",
|
| 148 |
+
"medium_delivery": "drone_env.graders.medium:grade_medium",
|
| 149 |
+
"hard_delivery": "drone_env.graders.hard:grade_hard",
|
| 150 |
}
|
| 151 |
|
| 152 |
@app.get("/grade/{task_name}")
|
|
|
|
| 207 |
@app.get("/rewards")
|
| 208 |
async def get_rewards():
|
| 209 |
"""Return the reward configuration for the current task."""
|
| 210 |
+
task_name = _env.state.task_name or "drone_env.graders.easy:grade_easy"
|
| 211 |
config = TASK_CONFIG.get(task_name, {})
|
| 212 |
# Filter only reward keys
|
| 213 |
rewards = {k: v for k, v in config.items() if k.startswith("r_")}
|
|
|
|
| 248 |
|
| 249 |
@app.post("/predict")
|
| 250 |
async def predict(obs: DroneObservation):
|
| 251 |
+
task_name = _env.state.task_name or "drone_env.graders.easy:grade_easy"
|
| 252 |
action_str = get_action_from_policy(obs, task_name)
|
| 253 |
return {"direction": action_str}
|
| 254 |
|
server/grid_world_environment.py
CHANGED
|
@@ -24,7 +24,7 @@ from drone_env.core.tasks import TASK_CONFIG
|
|
| 24 |
from drone_env.core.drone import compute_next_pos, drain_battery
|
| 25 |
from drone_env.core.obstacles import check_move
|
| 26 |
from drone_env.core.state_manager import new_episode_state
|
| 27 |
-
from drone_env.
|
| 28 |
from drone_env.rl.trainer import record_episode
|
| 29 |
|
| 30 |
|
|
@@ -33,7 +33,7 @@ class DroneDeliveryEnvironment(Environment):
|
|
| 33 |
def __init__(self):
|
| 34 |
super().__init__()
|
| 35 |
self._state = DroneState()
|
| 36 |
-
self._cfg = TASK_CONFIG["drone_env.
|
| 37 |
self._grid: List[List[str]] = []
|
| 38 |
self._deliveries: List[Tuple[int, int]] = []
|
| 39 |
self._delivered: List[bool] = []
|
|
@@ -51,11 +51,11 @@ class DroneDeliveryEnvironment(Environment):
|
|
| 51 |
|
| 52 |
# Map short task IDs (from openenv.yaml) to full grader keys
|
| 53 |
_TASK_ID_MAP = {
|
| 54 |
-
"easy_delivery": "drone_env.
|
| 55 |
-
"medium_delivery": "drone_env.
|
| 56 |
-
"hard_delivery": "drone_env.
|
| 57 |
}
|
| 58 |
-
task = "drone_env.
|
| 59 |
if action and action.task_name:
|
| 60 |
name = action.task_name
|
| 61 |
if name in _TASK_ID_MAP:
|
|
|
|
| 24 |
from drone_env.core.drone import compute_next_pos, drain_battery
|
| 25 |
from drone_env.core.obstacles import check_move
|
| 26 |
from drone_env.core.state_manager import new_episode_state
|
| 27 |
+
from drone_env.graders import GRADERS
|
| 28 |
from drone_env.rl.trainer import record_episode
|
| 29 |
|
| 30 |
|
|
|
|
| 33 |
def __init__(self):
|
| 34 |
super().__init__()
|
| 35 |
self._state = DroneState()
|
| 36 |
+
self._cfg = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
|
| 37 |
self._grid: List[List[str]] = []
|
| 38 |
self._deliveries: List[Tuple[int, int]] = []
|
| 39 |
self._delivered: List[bool] = []
|
|
|
|
| 51 |
|
| 52 |
# Map short task IDs (from openenv.yaml) to full grader keys
|
| 53 |
_TASK_ID_MAP = {
|
| 54 |
+
"easy_delivery": "drone_env.graders.easy:grade_easy",
|
| 55 |
+
"medium_delivery": "drone_env.graders.medium:grade_medium",
|
| 56 |
+
"hard_delivery": "drone_env.graders.hard:grade_hard",
|
| 57 |
}
|
| 58 |
+
task = "drone_env.graders.easy:grade_easy"
|
| 59 |
if action and action.task_name:
|
| 60 |
name = action.task_name
|
| 61 |
if name in _TASK_ID_MAP:
|
server/static/index.html
CHANGED
|
@@ -1230,17 +1230,17 @@
|
|
| 1230 |
<div class="panel-inner">
|
| 1231 |
<div class="panel-title">Mission Select</div>
|
| 1232 |
<div class="task-group" id="taskGroup">
|
| 1233 |
-
<button class="task-btn active" data-task="drone_env.
|
| 1234 |
<span class="dot"></span>
|
| 1235 |
Easy Delivery
|
| 1236 |
<span class="task-tag">10Γ10</span>
|
| 1237 |
</button>
|
| 1238 |
-
<button class="task-btn" data-task="drone_env.
|
| 1239 |
<span class="dot"></span>
|
| 1240 |
Medium Delivery
|
| 1241 |
<span class="task-tag">14Γ14</span>
|
| 1242 |
</button>
|
| 1243 |
-
<button class="task-btn" data-task="drone_env.
|
| 1244 |
<span class="dot"></span>
|
| 1245 |
Hard Delivery
|
| 1246 |
<span class="task-tag">18Γ18</span>
|
|
|
|
| 1230 |
<div class="panel-inner">
|
| 1231 |
<div class="panel-title">Mission Select</div>
|
| 1232 |
<div class="task-group" id="taskGroup">
|
| 1233 |
+
<button class="task-btn active" data-task="drone_env.graders.easy:grade_easy">
|
| 1234 |
<span class="dot"></span>
|
| 1235 |
Easy Delivery
|
| 1236 |
<span class="task-tag">10Γ10</span>
|
| 1237 |
</button>
|
| 1238 |
+
<button class="task-btn" data-task="drone_env.graders.medium:grade_medium">
|
| 1239 |
<span class="dot"></span>
|
| 1240 |
Medium Delivery
|
| 1241 |
<span class="task-tag">14Γ14</span>
|
| 1242 |
</button>
|
| 1243 |
+
<button class="task-btn" data-task="drone_env.graders.hard:grade_hard">
|
| 1244 |
<span class="dot"></span>
|
| 1245 |
Hard Delivery
|
| 1246 |
<span class="task-tag">18Γ18</span>
|
server/static/script.js
CHANGED
|
@@ -17,7 +17,7 @@ const EMOJI = {
|
|
| 17 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 18 |
// STATE
|
| 19 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
-
let currentTask = 'drone_env.
|
| 21 |
let autoTimer = null;
|
| 22 |
let logTimer = null;
|
| 23 |
let obs = null;
|
|
@@ -601,12 +601,12 @@ function startNextTask() {
|
|
| 601 |
closeCompletionModal();
|
| 602 |
|
| 603 |
const sequence = {
|
| 604 |
-
'drone_env.
|
| 605 |
-
'drone_env.
|
| 606 |
-
'drone_env.
|
| 607 |
};
|
| 608 |
|
| 609 |
-
const nextTask = sequence[currentTask] || 'drone_env.
|
| 610 |
currentTask = nextTask;
|
| 611 |
|
| 612 |
// Update active state on buttons
|
|
@@ -627,7 +627,7 @@ async function updateMissionLegend() {
|
|
| 627 |
if (!container || !data.tasks) return;
|
| 628 |
|
| 629 |
// Ensure tasks are sorted Easy, Medium, Hard
|
| 630 |
-
const order = ['drone_env.
|
| 631 |
const tasks = data.tasks.sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
|
| 632 |
|
| 633 |
container.innerHTML = `
|
|
|
|
| 17 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 18 |
// STATE
|
| 19 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
+
let currentTask = 'drone_env.graders.easy:grade_easy';
|
| 21 |
let autoTimer = null;
|
| 22 |
let logTimer = null;
|
| 23 |
let obs = null;
|
|
|
|
| 601 |
closeCompletionModal();
|
| 602 |
|
| 603 |
const sequence = {
|
| 604 |
+
'drone_env.graders.easy:grade_easy': 'drone_env.graders.medium:grade_medium',
|
| 605 |
+
'drone_env.graders.medium:grade_medium': 'drone_env.graders.hard:grade_hard',
|
| 606 |
+
'drone_env.graders.hard:grade_hard': 'drone_env.graders.easy:grade_easy'
|
| 607 |
};
|
| 608 |
|
| 609 |
+
const nextTask = sequence[currentTask] || 'drone_env.graders.easy:grade_easy';
|
| 610 |
currentTask = nextTask;
|
| 611 |
|
| 612 |
// Update active state on buttons
|
|
|
|
| 627 |
if (!container || !data.tasks) return;
|
| 628 |
|
| 629 |
// Ensure tasks are sorted Easy, Medium, Hard
|
| 630 |
+
const order = ['drone_env.graders.easy:grade_easy', 'drone_env.graders.medium:grade_medium', 'drone_env.graders.hard:grade_hard'];
|
| 631 |
const tasks = data.tasks.sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
|
| 632 |
|
| 633 |
container.innerHTML = `
|
tests/test_env.py
CHANGED
|
@@ -12,14 +12,14 @@ from drone_env.core.grid_generator import generate_city_map
|
|
| 12 |
from drone_env.core.tasks import TASK_CONFIG
|
| 13 |
from drone_env.core.obstacles import check_move
|
| 14 |
from drone_env.core.drone import compute_next_pos, drain_battery
|
| 15 |
-
from drone_env.
|
| 16 |
from drone_env.models import DroneState
|
| 17 |
|
| 18 |
|
| 19 |
# ββ Grid generator ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
|
| 21 |
def test_grid_shape():
|
| 22 |
-
cfg = TASK_CONFIG["
|
| 23 |
rng = torch.Generator(); rng.manual_seed(42)
|
| 24 |
grid, deliveries, start = generate_city_map(cfg, rng)
|
| 25 |
assert len(grid) == cfg["height"]
|
|
@@ -30,7 +30,7 @@ def test_grid_shape():
|
|
| 30 |
|
| 31 |
|
| 32 |
def test_start_on_road():
|
| 33 |
-
cfg = TASK_CONFIG["
|
| 34 |
rng = torch.Generator(); rng.manual_seed(7)
|
| 35 |
grid, _, start = generate_city_map(cfg, rng)
|
| 36 |
sx, sy = start
|
|
@@ -38,7 +38,7 @@ def test_start_on_road():
|
|
| 38 |
|
| 39 |
|
| 40 |
def test_delivery_cells_present():
|
| 41 |
-
cfg = TASK_CONFIG["
|
| 42 |
rng = torch.Generator(); rng.manual_seed(99)
|
| 43 |
grid, deliveries, _ = generate_city_map(cfg, rng)
|
| 44 |
for dx, dy in deliveries:
|
|
@@ -129,9 +129,9 @@ def test_grade_medium_hard_bounds():
|
|
| 129 |
# ββ Task configs ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 130 |
|
| 131 |
def test_all_tasks_present():
|
| 132 |
-
assert "
|
| 133 |
-
assert "
|
| 134 |
-
assert "
|
| 135 |
|
| 136 |
def test_task_has_required_keys():
|
| 137 |
required = ["width","height","n_buildings","n_trees","n_obstacles",
|
|
@@ -142,9 +142,9 @@ def test_task_has_required_keys():
|
|
| 142 |
assert key in cfg, f"{task} missing key: {key}"
|
| 143 |
|
| 144 |
def test_task_difficulty_ordering():
|
| 145 |
-
easy = TASK_CONFIG["
|
| 146 |
-
medium = TASK_CONFIG["
|
| 147 |
-
hard = TASK_CONFIG["
|
| 148 |
assert easy["width"] < medium["width"] < hard["width"]
|
| 149 |
assert easy["n_deliveries"] < medium["n_deliveries"] < hard["n_deliveries"]
|
| 150 |
assert easy["max_steps"] < medium["max_steps"] < hard["max_steps"]
|
|
|
|
| 12 |
from drone_env.core.tasks import TASK_CONFIG
|
| 13 |
from drone_env.core.obstacles import check_move
|
| 14 |
from drone_env.core.drone import compute_next_pos, drain_battery
|
| 15 |
+
from drone_env.graders import grade_easy, grade_medium, grade_hard
|
| 16 |
from drone_env.models import DroneState
|
| 17 |
|
| 18 |
|
| 19 |
# ββ Grid generator ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
|
| 21 |
def test_grid_shape():
|
| 22 |
+
cfg = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
|
| 23 |
rng = torch.Generator(); rng.manual_seed(42)
|
| 24 |
grid, deliveries, start = generate_city_map(cfg, rng)
|
| 25 |
assert len(grid) == cfg["height"]
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def test_start_on_road():
|
| 33 |
+
cfg = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
|
| 34 |
rng = torch.Generator(); rng.manual_seed(7)
|
| 35 |
grid, _, start = generate_city_map(cfg, rng)
|
| 36 |
sx, sy = start
|
|
|
|
| 38 |
|
| 39 |
|
| 40 |
def test_delivery_cells_present():
|
| 41 |
+
cfg = TASK_CONFIG["drone_env.graders.medium:grade_medium"]
|
| 42 |
rng = torch.Generator(); rng.manual_seed(99)
|
| 43 |
grid, deliveries, _ = generate_city_map(cfg, rng)
|
| 44 |
for dx, dy in deliveries:
|
|
|
|
| 129 |
# ββ Task configs ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 130 |
|
| 131 |
def test_all_tasks_present():
|
| 132 |
+
assert "drone_env.graders.easy:grade_easy" in TASK_CONFIG
|
| 133 |
+
assert "drone_env.graders.medium:grade_medium" in TASK_CONFIG
|
| 134 |
+
assert "drone_env.graders.hard:grade_hard" in TASK_CONFIG
|
| 135 |
|
| 136 |
def test_task_has_required_keys():
|
| 137 |
required = ["width","height","n_buildings","n_trees","n_obstacles",
|
|
|
|
| 142 |
assert key in cfg, f"{task} missing key: {key}"
|
| 143 |
|
| 144 |
def test_task_difficulty_ordering():
|
| 145 |
+
easy = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
|
| 146 |
+
medium = TASK_CONFIG["drone_env.graders.medium:grade_medium"]
|
| 147 |
+
hard = TASK_CONFIG["drone_env.graders.hard:grade_hard"]
|
| 148 |
assert easy["width"] < medium["width"] < hard["width"]
|
| 149 |
assert easy["n_deliveries"] < medium["n_deliveries"] < hard["n_deliveries"]
|
| 150 |
assert easy["max_steps"] < medium["max_steps"] < hard["max_steps"]
|