Buckets:
| """ | |
| TimeController — Slot-based turn synchronisation for the multi-agent CropRL env. | |
| Each calendar month is divided into K action slots. Every agent starts the | |
| month with a budget of K slots. The month only advances when **every** agent | |
| has either exhausted their budget or explicitly called End Turn (action 0). | |
| Design choices aligned with the implementation plan: | |
| - Fixed rotation: the "first agent" rotates each month to avoid positional bias. | |
| - Slot ordering within a month is fixed (agent 0, 1, … N-1 take turns), but | |
| the first active slot is awarded to a different agent each month. | |
| - Agents that call End Turn early simply wait (blocked) while others finish. | |
| """ | |
| from __future__ import annotations | |
| from typing import Dict, Optional | |
| class TurnOverError(Exception): | |
| """Raised when an agent tries to act after calling End Turn this month.""" | |
| class TimeController: | |
| """ | |
| Manages the shared calendar month and per-agent action budgets. | |
| Attributes | |
| ---------- | |
| month : int | |
| Current calendar month (1-12). | |
| year : int | |
| Current year (1-based). | |
| month_count : int | |
| Total months elapsed since episode start. | |
| """ | |
| def __init__(self, num_agents: int, action_slots_per_month: int) -> None: | |
| self.num_agents = num_agents | |
| self.action_slots_per_month = action_slots_per_month | |
| self.month: int = 1 | |
| self.year: int = 1 | |
| self.month_count: int = 0 | |
| # Per-agent bookkeeping | |
| self._slots_used: Dict[int, int] = {i: 0 for i in range(num_agents)} | |
| # Rotating first-agent index (changes each month, fair rotation) | |
| self._first_agent_offset: int = 0 | |
| # ────────────────────────────────────────────────────────────── | |
| # Public API | |
| # ────────────────────────────────────────────────────────────── | |
| def reset(self) -> None: | |
| """Reset the controller for a new episode.""" | |
| self.month = 1 | |
| self.year = 1 | |
| self.month_count = 0 | |
| self._first_agent_offset = 0 | |
| self._reset_month() | |
| def slots_remaining(self, agent_id: int) -> int: | |
| """Return how many action slots agent *agent_id* has left this month.""" | |
| return max(0, self.action_slots_per_month - self._slots_used[agent_id]) | |
| def is_turn_done(self, agent_id: int) -> bool: | |
| """Return True if the agent has exhausted their slots for this month.""" | |
| return self._slots_used[agent_id] >= self.action_slots_per_month | |
| def consume_slot(self, agent_id: int) -> None: | |
| """ | |
| Consume one action slot for *agent_id*. | |
| Raises | |
| ------ | |
| ValueError | |
| If the agent has no slots remaining (budget exhausted). | |
| """ | |
| if self._slots_used[agent_id] >= self.action_slots_per_month: | |
| raise TurnOverError( | |
| f"Agent {agent_id} has no action slots remaining this month." | |
| ) | |
| self._slots_used[agent_id] += 1 | |
| def all_done(self) -> bool: | |
| """Return True when every agent has exhausted their action slots.""" | |
| return all(self._slots_used[i] >= self.action_slots_per_month for i in range(self.num_agents)) | |
| def advance_month(self) -> bool: | |
| """ | |
| Advance the calendar by one month. Returns True when a year boundary | |
| is crossed (useful for triggering inflation in the outer env). | |
| """ | |
| old_month = self.month | |
| self.month = (self.month % 12) + 1 | |
| self.month_count += 1 | |
| year_advanced = False | |
| if self.month == 1 and old_month == 12: | |
| self.year += 1 | |
| year_advanced = True | |
| # Rotate the first-agent offset | |
| self._first_agent_offset = (self._first_agent_offset + 1) % self.num_agents | |
| self._reset_month() | |
| return year_advanced | |
| def current_slot_for(self, agent_id: int) -> int: | |
| """Return the slot index (0-based) the agent is *currently* on.""" | |
| return self._slots_used[agent_id] | |
| # ────────────────────────────────────────────────────────────── | |
| # Internal helpers | |
| # ────────────────────────────────────────────────────────────── | |
| def _reset_month(self) -> None: | |
| """Reset per-agent slot budgets for a new month.""" | |
| for i in range(self.num_agents): | |
| self._slots_used[i] = 0 | |
Xet Storage Details
- Size:
- 4.8 kB
- Xet hash:
- 6d26f12063200dc02096f21429a48db1ae969ca0d688947629704f2dbc0a22b3
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.