Skip to content

Commit 87259c8

Browse files
bing-jclaude
andcommitted
Fix PR #77 review findings: revert transition() override break, ndarray edge cases
Agent.transition()'s widened return type broke its override of Entity.transition() (reportIncompatibleMethodOverride), so it's reverted back to -> None; collect_turn_stats goes back to reconstructing agent stats from agent.model.memory tail-index arithmetic instead. Also hardens TensorboardLogger.record_step's np.ndarray branch against empty arrays (previously fell through to a crashing float()) and caps per-element scalar explosion for large arrays. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 4421789 commit 87259c8

3 files changed

Lines changed: 31 additions & 28 deletions

File tree

sorrel/agents/agent.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def model_take_action(self, state: np.ndarray):
152152
return threadsafe_model.threadsafe_take_action(state)
153153
return self.model.take_action(state)
154154

155-
def transition(self, world: W) -> tuple[int, float, bool]:
155+
def transition(self, world: W) -> None:
156156
"""Processes a full transition step for the agent.
157157
158158
This function does the following:
@@ -163,10 +163,6 @@ def transition(self, world: W) -> tuple[int, float, bool]:
163163
164164
Args:
165165
env (Gridworld): the environment that this agent is acting in.
166-
167-
Returns:
168-
tuple[int, float, bool]: the ``(action, reward, done)`` values computed
169-
for this transition.
170166
"""
171167
state = self.pov(world)
172168
action = self.get_action(state)
@@ -175,7 +171,6 @@ def transition(self, world: W) -> tuple[int, float, bool]:
175171

176172
world.total_reward += reward
177173
self.add_memory(state, action, reward, done)
178-
return action, reward, done
179174

180175

181176
class MovingAgent[W: Gridworld](Agent[W]):

sorrel/environment.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,10 @@ def take_turn(self, epoch: int = 0) -> None:
102102
x: Entity
103103
if x.has_transitions and not isinstance(x, Agent):
104104
x.transition(self.world)
105-
agent_results: list[tuple[int, float, bool]] = [
106-
agent.transition(self.world) for agent in self.agents
107-
]
105+
for agent in self.agents:
106+
agent.transition(self.world)
108107
if self._active_logger is not None:
109-
stats = self.collect_turn_stats(epoch, agent_results)
108+
stats = self.collect_turn_stats(epoch)
110109
self.on_turn_end(stats)
111110

112111
def _model_start_epoch_action(self, agent: Agent[W], epoch: int) -> None:
@@ -121,9 +120,7 @@ def _model_train_step(self, agent: Agent[W]) -> np.ndarray:
121120
"""Run model train step."""
122121
return agent.model.train_step()
123122

124-
def collect_turn_stats(
125-
self, epoch: int, agent_results: list[tuple[int, float, bool]]
126-
) -> TurnStats:
123+
def collect_turn_stats(self, epoch: int) -> TurnStats:
127124
"""Collect per-turn statistics after all transitions have run.
128125
129126
Called automatically by :meth:`take_turn` (when an active logger is
@@ -134,25 +131,25 @@ def collect_turn_stats(
134131
135132
Args:
136133
epoch: Current epoch index.
137-
agent_results: The ``(action, reward, done)`` tuples returned by
138-
each agent's :meth:`~sorrel.agents.agent.Agent.transition`
139-
call this turn, in the same order as :attr:`agents`.
140134
141135
Returns:
142136
A :class:`~sorrel.utils.turn_stats.TurnStats` snapshot for this turn.
143137
"""
144-
agent_stats: list[AgentTurnStats] = [
145-
AgentTurnStats(
146-
agent_id=i,
147-
location=tuple(agent.location),
148-
last_action=action,
149-
last_reward=reward,
150-
last_done=done,
151-
)
152-
for i, (agent, (action, reward, done)) in enumerate(
153-
zip(self.agents, agent_results)
138+
agent_stats: list[AgentTurnStats] = []
139+
for i, agent in enumerate(self.agents):
140+
mem = agent.model.memory
141+
if mem.size == 0:
142+
continue
143+
tail = (mem.idx - 1) % mem.capacity
144+
agent_stats.append(
145+
AgentTurnStats(
146+
agent_id=i,
147+
location=tuple(agent.location),
148+
last_action=int(mem.actions[tail]),
149+
last_reward=float(mem.rewards[tail]),
150+
last_done=bool(mem.dones[tail]),
151+
)
154152
)
155-
]
156153
return TurnStats(
157154
epoch=epoch,
158155
turn=self.turn,

sorrel/utils/logging.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ class TensorboardLogger(Logger):
189189
additional_values: A dictionary of optional values to be stored.
190190
"""
191191

192+
# Above this many elements, a multi-element np.ndarray extra is logged as a
193+
# single mean scalar instead of one TensorBoard series per element.
194+
_MAX_EXTRA_ARRAY_ELEMENTS = 32
195+
192196
def __init__(self, max_epochs: int, log_dir: str | os.PathLike, *args):
193197
"""Initialize a Tensorboard log.
194198
@@ -245,7 +249,14 @@ def record_step(self, stats: TurnStats) -> None:
245249
for key, value in stats.extra.items():
246250
if isinstance(value, dict):
247251
self.writer.add_scalars(f"turn/{key}", value, step)
248-
elif isinstance(value, np.ndarray) and value.size > 1:
252+
elif isinstance(value, np.ndarray) and value.size != 1:
253+
if value.size == 0:
254+
continue
255+
if value.size > self._MAX_EXTRA_ARRAY_ELEMENTS:
256+
self.writer.add_scalar(
257+
f"turn/{key}_mean", float(value.mean()), step
258+
)
259+
continue
249260
per_element = {str(i): float(v) for i, v in enumerate(value.ravel())}
250261
self.writer.add_scalars(f"turn/{key}", per_element, step)
251262
else:

0 commit comments

Comments
 (0)