Source code for openswmm_gymnasium.viz.figures.reward_curves
"""
Per-step reward and cumulative-reward line plot.
@author: Caleb Buahin
@copyright: Copyright (c) 2026 Caleb Buahin
@license: MIT
"""
from __future__ import annotations
import numpy as np
import plotly.graph_objects as go
from openswmm_gymnasium.viz.trajectory import Trajectory
[docs]
def reward_curves(
trajectory: Trajectory,
*,
rolling_window: int = 10,
title: str = "Reward curves",
) -> go.Figure:
"""Per-step reward + cumulative reward + rolling-mean overlay.
For scalar reward: three traces on one axis. For vector reward
(B{MO env}): one trace per objective, plus per-objective cumulative
traces on a secondary axis.
@param trajectory: Loaded episode.
@type trajectory: L{Trajectory}
@param rolling_window: Window size for the rolling-mean overlay.
Set to C{0} or C{1} to disable.
@type rolling_window: int
@param title: Figure title.
@type title: str
@rtype: L{plotly.graph_objects.Figure}
"""
r = trajectory.rewards
fig = go.Figure()
if r.size == 0:
return fig.update_layout(title=title)
if r.ndim == 1:
steps = np.arange(1, r.size + 1)
fig.add_trace(go.Scatter(x=steps, y=r, mode="lines", name="reward"))
fig.add_trace(go.Scatter(x=steps, y=np.cumsum(r), mode="lines", name="cumulative"))
if rolling_window > 1 and r.size >= rolling_window:
kernel = np.ones(rolling_window) / rolling_window
rm = np.convolve(r, kernel, mode="valid")
x_rm = steps[rolling_window - 1 :]
fig.add_trace(
go.Scatter(x=x_rm, y=rm, mode="lines", name=f"rolling mean ({rolling_window})")
)
else:
n_obj = r.shape[1]
steps = np.arange(1, r.shape[0] + 1)
for k in range(n_obj):
fig.add_trace(go.Scatter(x=steps, y=r[:, k], mode="lines", name=f"reward[{k}]"))
fig.add_trace(
go.Scatter(
x=steps,
y=np.cumsum(r[:, k]),
mode="lines",
name=f"cumulative[{k}]",
)
)
fig.update_layout(
title=title,
xaxis_title="env step",
yaxis_title="reward",
hovermode="x unified",
)
return fig