manikandan-n-07 commited on
Commit
33523d4
·
1 Parent(s): 675f070

Fix: Self-contained graders and spec compliance

Browse files
core/graders.py CHANGED
@@ -1,49 +1,69 @@
1
  """
2
  drone_delivery_env/core/graders.py
3
  Evaluation logic for scoring Drone Delivery missions.
 
 
 
 
 
4
  """
5
- from drone_env.models import DroneState
6
 
7
 
8
- def compute_grade(state: DroneState, max_steps: float) -> float:
 
 
 
 
 
 
 
9
  """
10
  Unified grade calculation:
11
- - 70% weighted by deliveries completed.
12
- - 30% weighted by efficiency (remaining battery and steps).
 
13
  """
14
- if state.deliveries_total == 0: return 1.0
15
-
16
- delivery_ratio = state.deliveries_done / state.deliveries_total
17
-
 
 
 
 
 
 
18
  # Efficiency factor (0.0 to 1.0)
19
- # Penalize if steps taken > average or battery is very low
20
- efficiency = state.battery * 0.5 + (1.0 - (state.step_count / max_steps)) * 0.5
21
  efficiency = max(0.0, min(1.0, efficiency))
22
-
23
  score = (delivery_ratio * 0.8) + (efficiency * 0.2)
24
-
25
- # If not all deliveries are done, the maximum score is capped
26
- if state.deliveries_done < state.deliveries_total:
27
  score = min(score, 0.49)
28
-
29
  # Hackathon Requirement: Score must be strictly between 0.0 and 1.0
30
  return max(0.01, min(0.99, float(score)))
31
 
32
 
33
- def grade_easy(state: DroneState) -> float:
 
34
  return compute_grade(state, 60.0)
35
 
36
 
37
- def grade_medium(state: DroneState) -> float:
 
38
  return compute_grade(state, 100.0)
39
 
40
 
41
- def grade_hard(state: DroneState) -> float:
 
42
  return compute_grade(state, 160.0)
43
 
44
 
45
  GRADERS = {
46
- "drone_env.core.graders:grade_easy": grade_easy,
47
  "drone_env.core.graders:grade_medium": grade_medium,
48
- "drone_env.core.graders:grade_hard": grade_hard
49
  }
 
1
  """
2
  drone_delivery_env/core/graders.py
3
  Evaluation logic for scoring Drone Delivery missions.
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):
15
+ return state.get(key, default)
16
+ return getattr(state, key, default)
17
+
18
+
19
+ def compute_grade(state, max_steps: float) -> float:
20
  """
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)
28
+ battery = _get_attr(state, "battery", 1.0)
29
+ step_count = _get_attr(state, "step_count", 0)
30
+
31
+ if deliveries_total == 0:
32
+ return 0.5 # neutral score when no deliveries configured
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
  }
data/memory.json CHANGED
@@ -1,54 +1,4 @@
1
  [
2
- {
3
- "task": "drone_env.core.graders:grade_easy",
4
- "steps": [
5
- {
6
- "step": 1,
7
- "x": 1,
8
- "y": 1,
9
- "action": "RIGHT",
10
- "reward": -0.05,
11
- "battery": 0.9833
12
- },
13
- {
14
- "step": 2,
15
- "x": 2,
16
- "y": 1,
17
- "action": "RIGHT",
18
- "reward": -0.1,
19
- "battery": 0.9667
20
- },
21
- {
22
- "step": 3,
23
- "x": 2,
24
- "y": 0,
25
- "action": "UP",
26
- "reward": -0.05,
27
- "battery": 0.95
28
- },
29
- {
30
- "step": 4,
31
- "x": 3,
32
- "y": 0,
33
- "action": "RIGHT",
34
- "reward": 0.95,
35
- "battery": 0.9333
36
- }
37
- ],
38
- "grid_meta": {
39
- "width": 10,
40
- "height": 10
41
- },
42
- "delivery_positions": [
43
- [
44
- 3,
45
- 0
46
- ]
47
- ],
48
- "deliveries_done": 1,
49
- "total_reward": 0.75,
50
- "total_steps": 4
51
- },
52
  {
53
  "task": "drone_env.core.graders:grade_easy",
54
  "steps": [
@@ -17940,5 +17890,59 @@
17940
  "deliveries_done": 3,
17941
  "total_reward": 5.9,
17942
  "total_steps": 20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17943
  }
17944
  ]
 
1
  [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  {
3
  "task": "drone_env.core.graders:grade_easy",
4
  "steps": [
 
17890
  "deliveries_done": 3,
17891
  "total_reward": 5.9,
17892
  "total_steps": 20
17893
+ },
17894
+ {
17895
+ "task": "drone_env.core.graders:grade_easy",
17896
+ "steps": [
17897
+ {
17898
+ "step": 1,
17899
+ "x": 4,
17900
+ "y": 2,
17901
+ "action": "LEFT",
17902
+ "reward": 0.1,
17903
+ "battery": 0.9833,
17904
+ "message": "On road \ud83d\udee3\ufe0f"
17905
+ },
17906
+ {
17907
+ "step": 2,
17908
+ "x": 3,
17909
+ "y": 2,
17910
+ "action": "LEFT",
17911
+ "reward": 0.1,
17912
+ "battery": 0.9667,
17913
+ "message": "On road \ud83d\udee3\ufe0f"
17914
+ },
17915
+ {
17916
+ "step": 3,
17917
+ "x": 3,
17918
+ "y": 1,
17919
+ "action": "UP",
17920
+ "reward": 0.1,
17921
+ "battery": 0.95,
17922
+ "message": "On road \ud83d\udee3\ufe0f"
17923
+ },
17924
+ {
17925
+ "step": 4,
17926
+ "x": 2,
17927
+ "y": 1,
17928
+ "action": "LEFT",
17929
+ "reward": 1.05,
17930
+ "battery": 0.9333,
17931
+ "message": "\u2705 Delivery 1/1 done!"
17932
+ }
17933
+ ],
17934
+ "grid_meta": {
17935
+ "width": 10,
17936
+ "height": 10
17937
+ },
17938
+ "delivery_positions": [
17939
+ [
17940
+ 2,
17941
+ 1
17942
+ ]
17943
+ ],
17944
+ "deliveries_done": 1,
17945
+ "total_reward": 1.35,
17946
+ "total_steps": 4
17947
  }
17948
  ]
openenv.yaml CHANGED
@@ -11,8 +11,3 @@ tasks:
11
  grader: drone_env.core.graders:grade_medium
12
  - id: hard_delivery
13
  grader: drone_env.core.graders:grade_hard
14
- graders:
15
- - id: drone_env.core.graders:grade_easy
16
- - id: drone_env.core.graders:grade_medium
17
- - id: drone_env.core.graders:grade_hard
18
-
 
11
  grader: drone_env.core.graders:grade_medium
12
  - id: hard_delivery
13
  grader: drone_env.core.graders:grade_hard
 
 
 
 
 
server/app.py CHANGED
@@ -90,17 +90,27 @@ async def step(action: DroneAction):
90
  async def get_state():
91
  return _env.state
92
 
 
 
 
 
 
 
 
93
  @app.get("/grade/{task_name}")
94
  async def grade(task_name: str):
95
- if task_name not in GRADERS:
 
 
96
  raise HTTPException(404, detail=f"Unknown task: {task_name}")
97
- return {"task": task_name, "score": float(GRADERS[task_name](_env.state))}
98
 
99
  @app.get("/analyse/{task_name}")
100
  async def analyse(task_name: str):
101
- if task_name not in TASK_CONFIG:
 
102
  raise HTTPException(404, detail=f"Unknown task: {task_name}")
103
- return PathLearner.analyse_episodes(task_name)
104
 
105
  @app.get("/path_history")
106
  async def path_history():
@@ -108,11 +118,24 @@ async def path_history():
108
 
109
  @app.get("/tasks")
110
  async def list_tasks():
111
- return {"tasks": [{"name": k, **v} for k, v in TASK_CONFIG.items()]}
 
 
 
 
 
112
 
113
  @app.get("/graders")
114
  async def list_graders():
115
- return {"graders": list(GRADERS.keys())}
 
 
 
 
 
 
 
 
116
 
117
  @app.get("/logs")
118
  async def get_logs():
 
90
  async def get_state():
91
  return _env.state
92
 
93
+ # Task ID → grader key mapping (matches openenv.yaml task ids)
94
+ TASK_ID_TO_GRADER = {
95
+ "easy_delivery": "drone_env.core.graders:grade_easy",
96
+ "medium_delivery": "drone_env.core.graders:grade_medium",
97
+ "hard_delivery": "drone_env.core.graders:grade_hard",
98
+ }
99
+
100
  @app.get("/grade/{task_name}")
101
  async def grade(task_name: str):
102
+ # Accept both short task IDs ("easy_delivery") and full grader keys
103
+ grader_key = TASK_ID_TO_GRADER.get(task_name, task_name)
104
+ if grader_key not in GRADERS:
105
  raise HTTPException(404, detail=f"Unknown task: {task_name}")
106
+ return {"task": task_name, "score": float(GRADERS[grader_key](_env.state))}
107
 
108
  @app.get("/analyse/{task_name}")
109
  async def analyse(task_name: str):
110
+ grader_key = TASK_ID_TO_GRADER.get(task_name, task_name)
111
+ if grader_key not in TASK_CONFIG:
112
  raise HTTPException(404, detail=f"Unknown task: {task_name}")
113
+ return PathLearner.analyse_episodes(grader_key)
114
 
115
  @app.get("/path_history")
116
  async def path_history():
 
118
 
119
  @app.get("/tasks")
120
  async def list_tasks():
121
+ return {"tasks": [{
122
+ "id": tid,
123
+ "name": tid,
124
+ "grader": gkey,
125
+ **TASK_CONFIG[gkey]
126
+ } for tid, gkey in TASK_ID_TO_GRADER.items()]}
127
 
128
  @app.get("/graders")
129
  async def list_graders():
130
+ # Return task IDs (the submission system expects these to match openenv.yaml task ids)
131
+ return {"graders": list(TASK_ID_TO_GRADER.keys())}
132
+
133
+ @app.post("/grade/{task_name}")
134
+ async def grade_post(task_name: str):
135
+ grader_key = TASK_ID_TO_GRADER.get(task_name, task_name)
136
+ if grader_key not in GRADERS:
137
+ raise HTTPException(404, detail=f"Unknown task: {task_name}")
138
+ return {"task": task_name, "score": float(GRADERS[grader_key](_env.state))}
139
 
140
  @app.get("/logs")
141
  async def get_logs():
server/grid_world_environment.py CHANGED
@@ -49,14 +49,24 @@ class DroneDeliveryEnvironment(Environment):
49
  if self._step_records and self._state.episode_id and not self._state.done:
50
  self._persist_episode()
51
 
 
 
 
 
 
 
52
  task = "drone_env.core.graders:grade_easy"
53
- if action and action.task_name and action.task_name in TASK_CONFIG:
54
- task = action.task_name
55
- elif action and action.task_name:
56
- # handle cases like "easy" instead of "easy_delivery"
57
- for k in TASK_CONFIG:
58
- if k.startswith(action.task_name):
59
- task = k; break
 
 
 
 
60
 
61
  self._cfg = TASK_CONFIG[task]
62
  self._rng.manual_seed(random.randint(0, 2**31))
 
49
  if self._step_records and self._state.episode_id and not self._state.done:
50
  self._persist_episode()
51
 
52
+ # Map short task IDs (from openenv.yaml) to full grader keys
53
+ _TASK_ID_MAP = {
54
+ "easy_delivery": "drone_env.core.graders:grade_easy",
55
+ "medium_delivery": "drone_env.core.graders:grade_medium",
56
+ "hard_delivery": "drone_env.core.graders:grade_hard",
57
+ }
58
  task = "drone_env.core.graders:grade_easy"
59
+ if action and action.task_name:
60
+ name = action.task_name
61
+ if name in _TASK_ID_MAP:
62
+ task = _TASK_ID_MAP[name]
63
+ elif name in TASK_CONFIG:
64
+ task = name
65
+ else:
66
+ # handle partial matches like "easy"
67
+ for k in TASK_CONFIG:
68
+ if k.startswith(name):
69
+ task = k; break
70
 
71
  self._cfg = TASK_CONFIG[task]
72
  self._rng.manual_seed(random.randint(0, 2**31))