RohitChandramouli6618 commited on
Commit
7537292
·
1 Parent(s): a5d21f8

Fix episodic memory: store relative district rank instead of absolute ID

Browse files
Files changed (1) hide show
  1. core/trajectory.py +55 -14
core/trajectory.py CHANGED
@@ -9,8 +9,18 @@ class EpisodicMemory:
9
  Stores high-reward steps from past rollouts and retrieves similar
10
  past decisions to guide the next rollout via prompt injection.
11
 
12
- Similarity is measured by L1 distance on infection profiles,
13
- with a small bonus for matching the episode phase (early/mid/late).
 
 
 
 
 
 
 
 
 
 
14
  """
15
 
16
  def __init__(self, max_size: int = 20):
@@ -24,17 +34,32 @@ class EpisodicMemory:
24
  phase = "early" if obs.current_step <= obs.max_steps // 3 else \
25
  "mid" if obs.current_step <= 2 * obs.max_steps // 3 else "late"
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  self.memories.append({
28
- "infection_profile": [round(d.reported_infection_rate, 2) for d in obs.districts],
29
- "resources": obs.available_resources,
30
- "phase": phase,
31
- "action_type": action.action_type,
32
- "district_id": action.district_id,
33
- "reward": round(reward, 4),
34
- "highest_district": max(
35
- range(len(obs.districts)),
36
- key=lambda i: obs.districts[i].reported_infection_rate
37
- ),
38
  })
39
 
40
  self.memories.sort(key=lambda m: m["reward"], reverse=True)
@@ -59,11 +84,27 @@ class EpisodicMemory:
59
  ranked = sorted(self.memories, key=score)
60
  top = ranked[:top_k]
61
 
 
 
 
 
 
 
 
62
  lines = ["Past decisions that earned positive reward (use as guidance):"]
63
  for m in top:
 
 
 
 
 
 
 
 
64
  lines.append(
65
- f" Phase={m.get('phase','?')} Profile={m['infection_profile']} resources={m['resources']}: "
66
- f"'{m['action_type']}' D{m['district_id']} → reward {m['reward']:+.2f}"
 
67
  )
68
  return "\n".join(lines)
69
 
 
9
  Stores high-reward steps from past rollouts and retrieves similar
10
  past decisions to guide the next rollout via prompt injection.
11
 
12
+ Key design choice: actions are stored as RELATIVE ranks rather than
13
+ absolute district IDs. Rank 0 = highest-infected district at the time
14
+ of the action, rank 1 = second-highest, and so on.
15
+
16
+ This matters because each episode randomises spread rates and densities,
17
+ so "allocate D1" from rollout 1 may refer to a completely different
18
+ epidemiological situation in rollout 2. Storing rank instead means
19
+ memory encodes the strategy ("target the worst district") rather than
20
+ an accident of episode initialisation ("target district 1").
21
+
22
+ On retrieval, ranks are resolved back to actual current district IDs
23
+ so the injected prompt text is immediately actionable.
24
  """
25
 
26
  def __init__(self, max_size: int = 20):
 
34
  phase = "early" if obs.current_step <= obs.max_steps // 3 else \
35
  "mid" if obs.current_step <= 2 * obs.max_steps // 3 else "late"
36
 
37
+ # Sort districts by infection rate descending to get current rankings
38
+ sorted_by_infection = sorted(
39
+ obs.districts,
40
+ key=lambda d: d.reported_infection_rate,
41
+ reverse=True
42
+ )
43
+ id_to_rank = {d.district_id: rank for rank, d in enumerate(sorted_by_infection)}
44
+
45
+ # Store rank rather than absolute ID
46
+ district_rank = id_to_rank.get(action.district_id, 0)
47
+
48
+ target_infection = next(
49
+ (d.reported_infection_rate for d in obs.districts if d.district_id == action.district_id),
50
+ 0.0
51
+ )
52
+ highest_infection = sorted_by_infection[0].reported_infection_rate if sorted_by_infection else 0.0
53
+
54
  self.memories.append({
55
+ "infection_profile": [round(d.reported_infection_rate, 2) for d in obs.districts],
56
+ "resources": obs.available_resources,
57
+ "phase": phase,
58
+ "action_type": action.action_type,
59
+ "district_rank": district_rank, # 0 = highest infected
60
+ "target_infection": round(target_infection, 2),
61
+ "highest_infection": round(highest_infection, 2),
62
+ "reward": round(reward, 4),
 
 
63
  })
64
 
65
  self.memories.sort(key=lambda m: m["reward"], reverse=True)
 
84
  ranked = sorted(self.memories, key=score)
85
  top = ranked[:top_k]
86
 
87
+ # Resolve stored ranks back to current district IDs for this episode
88
+ current_sorted = sorted(
89
+ obs.districts,
90
+ key=lambda d: d.reported_infection_rate,
91
+ reverse=True
92
+ )
93
+
94
  lines = ["Past decisions that earned positive reward (use as guidance):"]
95
  for m in top:
96
+ rank = m["district_rank"]
97
+ if rank < len(current_sorted):
98
+ resolved_id = current_sorted[rank].district_id
99
+ rank_label = f"rank-{rank} district (currently D{resolved_id})"
100
+ else:
101
+ resolved_id = current_sorted[0].district_id if current_sorted else 0
102
+ rank_label = f"rank-0 district (currently D{resolved_id})"
103
+
104
  lines.append(
105
+ f" Phase={m.get('phase','?')} resources={m['resources']} "
106
+ f"highest={m['highest_infection']:.2f} target={m['target_infection']:.2f}: "
107
+ f"'{m['action_type']}' {rank_label} → reward {m['reward']:+.2f}"
108
  )
109
  return "\n".join(lines)
110