factoriax.analysis.milestones#

Find when key events first happen in an episode, and plot them.

Two questions, each with a finder and its plots. When did each achievement unlock, and when did the agent first take a given action? The answers show how far a policy gets and how quickly, which is what makes two policies comparable.

Every timestep here is a 0-based index along the time axis of the trajectory. An event that never happened reports -1, so a caller masks with >= 0 before it takes a mean.

The two box plots drop the episodes where the event never happened, and put the share that did happen in the tick label. A box therefore describes the episodes that reached the event, and not the whole batch. A policy that reaches an achievement once, quickly, can look better than one that reaches it every time, slowly.

factoriax.analysis.milestones.achievement_timing(traj: Trajectory) → ndarray#

Find the first timestep at which each achievement unlocked.

Parameters:

traj – A trajectory whose achievements field holds a mask of shape (B, T, A). The mask is read as a boolean, so any non-zero value counts as unlocked.

Returns:

int32 of shape (B, A). Entry (b, a) is the first timestep at which achievement a was set in episode b, or -1 when it never was.

Return type:

numpy.ndarray

Raises:

ValueError – When the trajectory carries no achievements field.

Notes

The scan is a Python loop over every episode and achievement, so the cost grows with B times A.

factoriax.analysis.milestones.first_action_timestep(traj: Trajectory, action_id: int, player: int | None = None) → ndarray#

Find the first timestep at which one action was taken.

Parameters:
  • traj – The recording to read. Actions of shape (B, T), (B, T, P), and (B, T, 1) all work.

  • action_id – The action to look for.

  • player – Which player to read, or None for player 0.

Returns:

int32 of shape (B,). Entry b is the first timestep at which episode b took the action, or -1 when it never did.

Return type:

numpy.ndarray

Raises:

IndexError – When player names a slot the player axis does not have.

factoriax.analysis.milestones.plot_achievement_progress(traj: Trajectory, achievement_labels: list[str] | None = None, ax: Axes | None = None, figsize: tuple[float, float] = (12, 5), title: str | None = None) → tuple[Figure, Axes]#

Draw the share of episodes that unlocked each achievement.

Each line is a cumulative distribution over unlock time. At timestep t it gives the share of all episodes that had unlocked that achievement by t.

The share is over every episode, and not only over the episodes that unlocked the achievement. A line therefore flattens below 1.0 when some episodes never reached it, and that height is the success rate. This differs from plot_achievement_timing(), which drops the episodes that never reached the achievement.

Parameters:
  • traj – A trajectory with an achievements field.

  • achievement_labels – The display name of each achievement, or None for "Achievement 0" and so on.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

factoriax.analysis.milestones.plot_achievement_timing(traj: Trajectory, achievement_labels: list[str] | None = None, ax: Axes | None = None, figsize: tuple[float, float] = (10, 5), title: str | None = None) → tuple[Figure, Axes]#

Draw one box for each achievement, over its unlock timesteps.

An achievement that no episode unlocked gets no box. For the rest, the box covers the episodes that did unlock it, and the tick label gives that share as a percentage. Read the box and the percentage together: a low percentage means the box describes few episodes.

Parameters:
  • traj – A trajectory with an achievements field.

  • achievement_labels – The display name of each achievement, or None for "Achievement 0" and so on. The list must be at least as long as the achievement axis.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple

factoriax.analysis.milestones.plot_first_action_timing(traj: Trajectory, action_ids: list[int], action_labels: list[str] | None = None, player: int | None = None, ax: Axes | None = None, figsize: tuple[float, float] = (10, 4), title: str | None = None) → tuple[Figure, Axes]#

Draw one box for each action, over its first-use timesteps.

This answers when an agent first mines, first crafts, or first places a machine.

An action that no episode took gets no box. For the rest, the box covers the episodes that did take it, and the tick label gives that share as a percentage. Read the box and the percentage together.

Parameters:
  • traj – The recording to read.

  • action_ids – The actions to draw, one box each.

  • action_labels – The display name of each action, or None to look the names up in DEFAULT_ACTION_LABELS. The list must be as long as action_ids.

  • player – Which player to read, or None for player 0.

  • ax – The axes to draw on, or None for a new figure.

  • figsize – The size of that new figure in inches. It is ignored when ax is given.

  • title – The figure title, or None for a default.

Returns:

(figure, axes). The figure is not written and not closed, so the caller owns it.

Return type:

tuple