Source code for pytes.plotting

#
# Standard plots
#
"""
Standard plots (toolkit, DESIGN D15), for one solution or several to compare. They use only
the outputs every model provides, so they work for any model and family, and need the
``plot`` extra (matplotlib).

- :func:`plot_summary`: inlet and outlet temperature, power and discharged energy against
  time.
- :func:`plot_power_against_energy`: power against discharged energy, the key comparison
  plot.
- :func:`plot_fluid_profiles`: the fluid temperature along the flow path at given times.

Each returns the figure and its axes, for further styling.

The PyTES plot colours (DESIGN D15) are available, not yet the default:

- the colour cycles :data:`THERMAL` and :data:`THERMAL_DARK`, also as the matplotlib
  styles ``"pytes.thermal"`` and ``"pytes.thermal_dark"``
  (``plt.style.use("pytes.thermal")``);
- the colourmaps ``"pytes.ember"`` (sequential) and ``"pytes.thaw"`` (diverging), and their
  reversals ``"pytes.ember_r"`` and ``"pytes.thaw_r"``, registered with matplotlib when this
  module is imported (``cmap="pytes.ember"``).
"""

import importlib.resources

import numpy as np

#: The colour cycle "PyTES Thermal", for light backgrounds (style ``"pytes.thermal"``)
THERMAL = ("#E8590C", "#1F4E9C", "#14A08A", "#F0A930", "#A23B72", "#88C7F0", "#36312E")

#: The colour cycle "PyTES Thermal (dark)", for dark backgrounds (style
#: ``"pytes.thermal_dark"``)
THERMAL_DARK = (
    "#F76707",
    "#6785F9",
    "#04B68B",
    "#FFC66C",
    "#B06C99",
    "#B2D5F8",
    "#D9D0C7",
)

#: The colourmaps registered with matplotlib, each also reversed (name ending in ``_r``)
COLOURMAPS = ("pytes.ember", "pytes.thaw")


[docs] def register_colourmaps(): """ Register the PyTES colourmaps (:data:`COLOURMAPS`) and their reversals with matplotlib. Done when :mod:`pytes.plotting` is imported, if matplotlib is installed; registering again does nothing. """ import matplotlib from matplotlib.colors import ListedColormap data = importlib.resources.files("pytes") / "colourmaps" for name in COLOURMAPS: if name in matplotlib.colormaps: continue with (data / f"{name.removeprefix('pytes.')}.csv").open() as file: colours = np.loadtxt(file, delimiter=",", skiprows=1) colourmap = ListedColormap(colours, name=name) matplotlib.colormaps.register(colourmap) matplotlib.colormaps.register(colourmap.reversed())
def _pyplot(): try: import matplotlib.pyplot as plt except ImportError as error: # pragma: no cover - depends on the environment raise ImportError( "pytes.plotting needs matplotlib: install the plot extra, " "pip install 'pytes-thermal[plot]'" ) from error return plt def _as_list(solutions, labels): """The solutions as a list, with a label for each (default: the model names)""" if not isinstance(solutions, list | tuple): solutions = [solutions] if labels is None: labels = [solution.all_models[0].name for solution in solutions] if len(labels) != len(solutions): raise ValueError(f"{len(labels)} labels for {len(solutions)} solutions") return list(solutions), list(labels) def _from_zero(ax): """Scale the y axis to include zero, without an offset in the tick labels""" ax.ticklabel_format(axis="y", useOffset=False) low, high = ax.dataLim.intervaly low, high = min(low, 0), max(high, 0) margin = 0.05 * (high - low) or 1 ax.set_ylim(low - margin if low < 0 else 0, high + margin if high > 0 else 0)
[docs] def plot_summary(solutions, labels=None): """ Inlet and outlet temperature, power and discharged energy against time. Parameters ---------- solutions : pybamm.Solution or list of them The solutions to plot. labels : list of str, optional A label for each solution (default: the model names). Returns ------- fig : matplotlib.figure.Figure axes : numpy.ndarray of matplotlib.axes.Axes Temperatures, power, and discharged energy. """ plt = _pyplot() solutions, labels = _as_list(solutions, labels) fig, axes = plt.subplots(1, 3, figsize=(12, 3.5), layout="constrained") for solution, label in zip(solutions, labels, strict=True): t = solution["Time [h]"].entries (line,) = axes[0].plot( t, solution["Outlet temperature [degC]"].entries, label=f"{label}, outlet" ) axes[0].plot( t, solution["Inlet temperature [degC]"].entries, "--", color=line.get_color(), label=f"{label}, inlet", ) axes[1].plot(t, solution["Power [kW]"].entries, label=label) axes[2].plot(t, solution["Discharged energy [kWh]"].entries, label=label) for ax, ylabel in zip( axes, ["Temperature [°C]", "Power [kW]", "Discharged energy [kWh]"], strict=True, ): ax.set_xlabel("Time [h]") ax.set_ylabel(ylabel) # A held power is a flat line at its value, not solver noise around an offset for ax in axes[1:]: _from_zero(ax) axes[0].legend(fontsize="small") if len(solutions) > 1: axes[1].legend(fontsize="small") return fig, axes
[docs] def plot_power_against_energy(solutions, labels=None): """ Power against discharged energy (DESIGN D15), the key comparison between designs: how the power a device delivers falls as it gives up its energy. Parameters ---------- solutions : pybamm.Solution or list of them The solutions to plot. labels : list of str, optional A label for each solution (default: the model names). Returns ------- fig : matplotlib.figure.Figure ax : matplotlib.axes.Axes """ plt = _pyplot() solutions, labels = _as_list(solutions, labels) fig, ax = plt.subplots(figsize=(5, 3.5), layout="constrained") for solution, label in zip(solutions, labels, strict=True): ax.plot( solution["Discharged energy [kWh]"].entries, solution["Power [kW]"].entries, label=label, ) ax.set_xlabel("Discharged energy [kWh]") ax.set_ylabel("Power [kW]") _from_zero(ax) ax.legend(fontsize="small") return fig, ax
[docs] def plot_fluid_profiles(solution, times, label=None): """ The fluid temperature along the flow path at given times. Parameters ---------- solution : pybamm.Solution The solution. times : list of float Times [s]. label : str, optional Title of the plot (default: the model name). Returns ------- fig : matplotlib.figure.Figure ax : matplotlib.axes.Axes """ plt = _pyplot() fluid = solution["Heat transfer fluid temperature [degC]"] z = fluid.mesh.nodes fig, ax = plt.subplots(figsize=(5, 3.5), layout="constrained") for t in times: ax.plot(z, np.ravel(fluid(t=t, z=z)), label=f"{t / 60:g} min") ax.set_xlabel("Distance along the flow path [m]") ax.set_ylabel("Fluid temperature [°C]") ax.set_title(label or solution.all_models[0].name) ax.legend(fontsize="small") return fig, ax
try: import matplotlib # noqa: F401 except ImportError: # pragma: no cover - depends on the environment pass else: register_colourmaps()