Skip to content

Visualisation API

Visualisation APIs cover reward/safety plots, episodic plots, shield telemetry, legends, and styling helpers used by notebooks.

Reward and Safety Figures

src.visualisation.reward_safety.plot_cum_reward_and_safety_side_by_side

plot_cum_reward_and_safety_side_by_side(
    *,
    figure_title: str | None = None,
    smooth=False,
    smooth_kind="moving",
    smooth_window=21,
    ema_alpha=0.15,
    smooth_points=800,
    show_seed_traces=False,
    show_level_changes: bool = True,
    level_line_aggregate: str = "median",
    annotate_level_changes: bool = True,
    wm_hist: list[Iterable] | None = None,
    wm_prepend_to: str | Iterable[str] = "IPPO_Shielded",
    show_world_model_line: bool = True,
    wm_line_aggregate: str = "median",
    annotate_world_model_line: bool = True,
    truncate_to_smallest: bool = False,
    band: str = "ci95",
    include_legend: bool = True,
    paper_panel: bool = False,
    save_path: str | None = None,
    **alg_histories,
)
Example

plot_cum_reward_and_safety_side_by_side( IPPO=ippo_histories, IPPO_Lag=ippo_lag_histories, smooth=True, smooth_kind="moving", smooth_window=21, smooth_points=800 )

src.visualisation.reward_safety_episodic.plot_reward_and_safety_episodic

plot_reward_and_safety_episodic(
    *,
    figure_title: str | None = None,
    smooth: bool = False,
    smooth_kind: str = "moving",
    smooth_window: int = 21,
    ema_alpha: float = 0.15,
    smooth_points: int = 800,
    show_seed_traces: bool = False,
    reward_per_step: bool = False,
    safety_per_step: bool = False,
    show_level_changes: bool = True,
    level_line_aggregate: str = "median",
    annotate_level_changes: bool = True,
    wm_hist: list[Iterable] | None = None,
    wm_prepend_to: str | Iterable[str] = "IPPO_Shielded",
    show_world_model_line: bool = True,
    wm_line_aggregate: str = "median",
    annotate_world_model_line: bool = True,
    truncate_to_smallest: bool = False,
    band: str = "ci95",
    include_legend: bool = True,
    paper_panel: bool = False,
    save_path: str | None = None,
    **alg_histories,
)

Episodic view:

Left panel: per-episode violations Right panel: per-episode reward

For each episode

x = t_end (global timestep where the episode terminated) y = cum_violations or cum_reward for that episode.

If *_per_step=True, y is divided by ep_len (episode length) so you get average per-step violation / reward within the episode.

src.visualisation.reward_safety_episodic.plot_episode_metric_series

plot_episode_metric_series(
    *,
    metric_key: str,
    ylabel: str,
    title: str = "",
    smooth: bool = False,
    smooth_kind: str = "moving",
    smooth_window: int = 21,
    ema_alpha: float = 0.15,
    smooth_points: int = 800,
    show_seed_traces: bool = False,
    wm_hist: list[Iterable] | None = None,
    wm_prepend_to: str | Iterable[str] = "IPPO_Shielded",
    truncate_to_smallest: bool = False,
    band: str = "ci95",
    include_legend: bool = True,
    paper_panel: bool = False,
    lower_clip: float | None = None,
    save_path: str | None = None,
    **alg_histories,
)

Plot one completed-episode metric against each episode's end timestep.

src.visualisation.reward_safety_episodic.export_episode_graph_variants

export_episode_graph_variants(
    output_dir: str | Path,
    *,
    episode_return_key: str = "cum_reward",
    episode_safety_key: str = "cum_violations",
    episode_return_ylabel: str = "Episode Return",
    episode_safety_ylabel: str = "Episode Safety Violations",
    smooth: bool = True,
    smooth_kind: str = "moving",
    smooth_window: int = 21,
    ema_alpha: float = 0.15,
    smooth_points: int = 800,
    show_seed_traces: bool = False,
    wm_hist: list[Iterable] | None = None,
    wm_prepend_to: str | Iterable[str] = "IPPO_Shielded",
    truncate_to_smallest: bool = False,
    band: str = "ci95",
    **alg_histories,
) -> dict[str, Path]

Export titleless completed-episode graph variants for all and shielded runs.

src.visualisation.shield_telemetry.plot_shield_telemetry

plot_shield_telemetry(
    *,
    figure_title: str | None = None,
    smooth: bool = False,
    smooth_kind: str = "moving",
    smooth_window: int = 21,
    smooth_points: int = 800,
    ema_alpha: float = 0.15,
    band: str = "ci95",
    include_legend: bool = True,
    paper_panel: bool = False,
    save_path: str | None = None,
    **alg_histories,
)

Opponent-Level Figures

src.visualisation.levels.extract_bayesian_level_series

extract_bayesian_level_series(
    history_one_seed: Iterable,
) -> tuple[np.ndarray, np.ndarray]

Reconstruct the unconstrained Bayesian posterior-mode step series.

src.visualisation.levels.extract_monotone_floor_series

extract_monotone_floor_series(
    history_one_seed: Iterable,
) -> tuple[np.ndarray, np.ndarray]

src.visualisation.levels.plot_om_bayesian_levels

plot_om_bayesian_levels(
    *,
    figure_title: str | None = None,
    include_legend: bool = True,
    save_path: str | Path | None = None,
    show: bool = False,
    smooth: bool = False,
    smooth_kind: str = "moving",
    smooth_window: int = 21,
    smooth_points: int = 800,
    ema_alpha: float = 0.15,
    band: str = "ci95",
    paper_panel: bool = False,
    **alg_histories: Mapping[str, list[Iterable]],
)

Plot raw Bayesian OM modes and the shield's separate monotone floor.

One faint step line is shown per seed so decreases and switches are not hidden by averaging. Plotting reads episode logs only and performs no new environment interactions.

Legends

src.visualisation.legend.ExperimentLegendVariant dataclass

ExperimentLegendVariant(
    slug: str, algorithm_labels: tuple[str, ...]
)

src.visualisation.legend.build_experiment_legend_handles

build_experiment_legend_handles(
    algorithm_labels: tuple[str, ...],
    *,
    include_world_model_line: bool = True,
    include_shield_level_change: bool = True,
) -> list[Line2D]

src.visualisation.legend.export_experiment_legend

export_experiment_legend(
    save_path: str | Path,
    *,
    algorithm_labels: tuple[str, ...],
    include_world_model_line: bool = True,
    include_shield_level_change: bool = True,
    single_row: bool = False,
) -> Path

src.visualisation.legend.export_experiment_legend_variants

export_experiment_legend_variants(
    export_dir: str | Path = "exports/legends",
) -> dict[str, Path]

Shared Plot Helpers

src.visualisation.common.build_seed_series

build_seed_series(
    history_one_seed: Iterable, metric_key: str
) -> pd.DataFrame

src.visualisation.common.summarize_band

summarize_band(
    histories_list: list[Iterable],
    metric_key: str,
    *,
    band: str = "minmax",
)

Align seeds on the union timestep grid; forward-fill within each seed range. Returns grid, mean, lower, upper.

band
  • "minmax": lower=min across seeds, upper=max across seeds
  • "std": lower=mean-std, upper=mean+std
  • "ci95": 95% CI for the mean: mean ± tcrit * (std / sqrt(n))

src.visualisation.common.smooth_curve

smooth_curve(
    x,
    y,
    kind="moving",
    window=21,
    ema_alpha=0.15,
    points=600,
)

Resample to a uniform grid then smooth. kind: "moving" (centered moving average) or "ema" (exponential). window: odd window size for 'moving'. points: number of x samples.

src.visualisation.common.draw_event_line

draw_event_line(
    ax,
    t: float,
    *,
    label: str,
    kind: str = "dotted",
    annotate: str | None = None,
    color: str = "0.15",
    lw: float | None = None,
    alpha: float | None = None,
    zorder: int = 1,
    dashes: tuple[float, float] | None = None,
    text_kwargs: dict | None = None,
)

src.visualisation.common.maybe_save_show

maybe_save_show(
    fig, *, save_path: str | None = None, show: bool = False
) -> None

Styling

src.visualisation.styling.use_paper_style

use_paper_style() -> None

src.visualisation.styling.use_paper_panel_style

use_paper_panel_style() -> None

src.visualisation.styling.format_step_axis

format_step_axis(ax, *, paper_panel: bool = False) -> None

src.visualisation.styling.style_axis_like_reference

style_axis_like_reference(
    ax, *, paper_panel: bool = False
) -> None