Source code for wombat.utilities.plot

"""Provides expoerimental plotting routines to help with simulation diagnostics."""

import numpy as np
import pandas as pd
import networkx as nx
import matplotlib as mpl
import matplotlib.pyplot as plt

from wombat import Simulation
from wombat.windfarm import Windfarm


[docs] def plot_farm_layout( windfarm: Windfarm, figure_kwargs: dict | None = None, plot_kwargs: dict | None = None, return_fig: bool = False, ) -> None | tuple[plt.figure, plt.axes]: """Plot the graph representation of the windfarm as represented through WOMBAT. Args: figure_kwargs : dict, optional Customized keyword arguments for matplotlib figure instantiation that will passed as ``plt.figure(**figure_kwargs). Defaults to {}.`` plot_kwargs : dict, optional Customized parameters for ``networkx.draw()`` that can will passed as ``nx.draw(**figure_kwargs)``. Defaults to ``with_labels=True``, ``horizontalalignment=right``, ``verticalalignment=bottom``, ``font_weight=bold``, ``font_size=10``, and ``node_color=#E37225``. return_fig : bool, optional Whether or not to return the figure and axes objects for further editing and/or saving. Defaults to False. Returns ------- None | tuple[plt.figure, plt.axes]: _description_ """ # Set the defaults for plotting if figure_kwargs is None: figure_kwargs = {} if plot_kwargs is None: plot_kwargs = {} figure_kwargs.setdefault("figsize", (14, 12)) figure_kwargs.setdefault("dpi", 200) plot_kwargs.setdefault("with_labels", True) plot_kwargs.setdefault("horizontalalignment", "right") plot_kwargs.setdefault("verticalalignment", "bottom") plot_kwargs.setdefault("font_weight", "bold") plot_kwargs.setdefault("font_size", 10) plot_kwargs.setdefault("node_color", "#E37225") fig = plt.figure(**figure_kwargs) ax = fig.add_subplot(111) # Get the node positions and all related edges, except the self-connected ones positions = { name: np.array([node["longitude"], node["latitude"]]) for name, node in windfarm.graph.nodes(data=True) } edges = [el for el in windfarm.graph.edges if el[0] != el[1]] nx.draw(windfarm.graph, pos=positions, edgelist=edges, ax=ax, **plot_kwargs) fig.tight_layout() plt.show() if return_fig: return fig, ax return None
[docs] def plot_farm_availability( sim: Simulation, which: str = "energy", individual_turbines: bool = False, farm_95_CI: bool = False, figure_kwargs: dict | None = None, plot_kwargs: dict | None = None, legend_kwargs: dict | None = None, tick_fontsize: int = 12, label_fontsize: int = 16, return_fig: bool = False, ) -> tuple[plt.Figure | plt.Axes] | None: """Plots a line chart of the monthly availability at the wind farm level. Parameters ---------- sim : Simulation A ``Simulation`` object that has been run. which : str One of "time" or "energy", to indicate the basis for the availability calculation, by default "energy". individual_turbines : bool, optional Indicates if faint gray lines should be added in the background for the availability of each turbine, by default False. farm_95_CI : bool, optional Indicates if the 95% CI area fill should be added in the background. figure_kwargs : dict, optional Custom parameters for ``plt.figure()``, by default ``figsize=(15, 7)`` and ``dpi=300``. plot_kwargs : dict, optional Custom parameters to be passed to ``ax.plot()``, by default a label consisting of the simulation name and project-level availability. legend_kwargs : dict, optional Custom parameters to be passed to ``ax.legend()``, by default ``fontsize=14``. tick_fontsize : int, optional The x- and y-axis tick label fontsize, by default 12. label_fontsize : int, optional The x- and y-axis label fontsize, by default 16. return_fig : bool, optional If ``True``, return the figure and Axes object, otherwise don't, by default False. Returns ------- tuple[plt.Figure, plt.Axes] | None See :py:attr:`return_fig` for details. _description_ """ # Get the availability data metrics = sim.metrics if which == "time": availability = ( metrics.time_based_availability("project", "windfarm") .astype(float) .values[0][0] ) windfarm_availability = metrics.time_based_availability( "month-year", "windfarm" ).astype(float) turbine_availability = metrics.time_based_availability("month-year", "turbine") label = f"{sim.env.simulation_name} Time-Based Availability: {availability:.2%}" elif which == "energy": availability = ( metrics.production_based_availability("project", "windfarm") .astype(float) .values[0][0] ) windfarm_availability = metrics.production_based_availability( "month-year", "windfarm" ).astype(float) turbine_availability = metrics.production_based_availability( "month-year", "turbine" ).astype(float) label = ( f"{sim.env.simulation_name} Energy-Based Availability: {availability:.2%}" ) else: raise ValueError("`which` must be one of 'energy' or 'time'.") # Set the defaults if figure_kwargs is None: figure_kwargs = {} if plot_kwargs is None: plot_kwargs = {} if legend_kwargs is None: legend_kwargs = {} figure_kwargs.setdefault("figsize", (15, 7)) figure_kwargs.setdefault("dpi", 300) plot_kwargs.setdefault("label", label) legend_kwargs.setdefault("fontsize", 14) fig = plt.figure(**figure_kwargs) ax = fig.add_subplot(111) x = range(windfarm_availability.shape[0]) # Individual turbine availability lines if individual_turbines: for turbine in turbine_availability.columns: ax.plot( x, turbine_availability[turbine].values, color="lightgray", alpha=0.75, linewidth=0.25, ) # CI error bars surrounding the lines if farm_95_CI: N = turbine_availability.shape[1] Z90, Z95, Z99 = (1.64, 1.96, 2.57) # noqa: F841 tm = turbine_availability.values.mean(axis=1) tsd = turbine_availability.values.std(axis=1) ci_lo = tm - Z95 * (tsd / np.sqrt(N)) ci_hi = tm + Z95 * (tsd / np.sqrt(N)) ax.fill_between( x, ci_lo, ci_hi, alpha=0.5, label="95% CI for Individual Turbines" ) ax.plot( x, windfarm_availability.windfarm.values, **plot_kwargs, ) years = list(range(sim.env.start_year, sim.env.end_year + 1)) xticks_major = [x * 12 for x in range(len(years))] xticks_minor = list(range(0, 12 * len(years), 3)) xlabels_major = [f"{year:>6}" for year in years] xlabels_minor = ["", "Apr", "", "Oct"] * len(years) ax.set_ylim(0, 1) ax.set_yticks(np.linspace(0, 1, 11)) ax.set_yticklabels([f"{y:.0%}" for y in ax.get_yticks()], fontsize=tick_fontsize) ax.set_xlim(0, windfarm_availability.shape[0]) ax.set_xticks(xticks_major) for t in ax.get_xticklabels(): t.set_y(-0.05) ax.set_xticks(xticks_minor, minor=True) ax.set_xticklabels(xlabels_major, ha="left", fontsize=tick_fontsize) ax.set_xticklabels(xlabels_minor, minor=True, rotation=90) ax.set_xlabel("Simulation Time", fontsize=label_fontsize) ax.set_ylabel("Monthly Availability", fontsize=label_fontsize) ax.legend(**legend_kwargs) ax.grid(axis="both", which="major") ax.grid(alpha=0.5, which="minor") fig.tight_layout() if return_fig: return fig, ax # type: ignore return None
[docs] def plot_operational_levels( sim: Simulation, figure_kwargs: dict | None = None, cbar_label_fontsize: int = 14, return_fig: bool = False, ): """Plots an hourly view of the operational levels of the wind farm and individual turbines as a heatmap. Parameters ---------- sim : Simulation A ``Simulation`` object that has been run. figure_kwargs : dict, optional Custom settings for ``plt.figure()``, by default ``figsize=(15, 10)`` and ``dpi=300``. cbar_label_fontsize : int, optional The default fontsize used in the color bar legend for the axis label, by default 14. return_fig : bool, optional If ``True``, return the figure and Axes object, otherwise don't, by default False. Returns ------- tuple[plt.Figure, plt.Axes] | None See :py:attr:`return_fig` for details. """ # Set the defaults if figure_kwargs is None: figure_kwargs = {} figure_kwargs.setdefault("figsize", (15, 10)) figure_kwargs.setdefault("dpi", 300) # Get the requisite data op = sim.metrics.operations x_ix = [ c for c in op.columns if c not in ("env_datetime", "env_time", "year", "month", "day") ] turbines = op[x_ix].values.astype(float).T env_time = op.env_time.values env_datetime = op.env_datetime.reset_index(drop=True) fig = plt.figure(**figure_kwargs) ax = fig.add_subplot(111) # Create the custom color map bounds = [0, 0.05, 0.5, 0.75, 0.9, 0.95, 1] YlGnBu_discrete7 = mpl.colors.ListedColormap( ["#ffffcc", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#0c2c84"] ) norm = mpl.colors.BoundaryNorm(bounds, YlGnBu_discrete7.N) # Plot the turbine availability ax.imshow( turbines, aspect="auto", cmap=YlGnBu_discrete7, norm=norm, interpolation="none" ) # Format the y-axis ax.set_yticks(np.arange(len(x_ix))) ax.set_yticklabels(x_ix) ax.set_ylim(-0.5, len(x_ix) - 0.5) ax.hlines( np.arange(len(x_ix)) + 0.5, env_time.min(), env_time.max(), color="white", linewidth=1, ) # Create the major x-tick data env_dt_yr = env_datetime.dt.year ix = ~env_dt_yr.duplicated() x_major_ticks = env_dt_yr.loc[ix].index[1:] x_major_ticklabels = env_dt_yr.loc[ix].values[1:] # Create the minor x-tick data env_datetime_df = env_datetime.to_frame() env_datetime_df["month"] = env_datetime_df.env_datetime.dt.month env_datetime_df["month_year"] = pd.DatetimeIndex( env_datetime_df.env_datetime ).to_period("m") ix = ~env_datetime_df.month_year.duplicated() & ( env_datetime_df.month.isin((4, 7, 10)) ) x_minor_ticks = env_datetime_df.loc[ix].index.values x_minor_ticklabels = [ "" if x == "Jul" else x for x in env_datetime_df.loc[ix].env_datetime.dt.strftime("%b") ] # Set the x-ticks ax.set_xticks(x_major_ticks) ax.set_xticklabels(x_major_ticklabels) for t in ax.get_xticklabels(): t.set_y(-0.05) ax.set_xticks(x_minor_ticks, minor=True) ax.set_xticklabels(x_minor_ticklabels, minor=True, rotation=90) # Create a color bar legend that is propportionally spaced cbar = ax.figure.colorbar( mpl.cm.ScalarMappable(cmap=YlGnBu_discrete7, norm=norm), ax=ax, ticks=bounds, spacing="proportional", format=mpl.ticker.PercentFormatter(xmax=1), ) cbar.ax.set_ylabel( "Availability", rotation=-90, va="bottom", fontsize=cbar_label_fontsize ) ax.grid(axis="x", which="major") ax.grid(alpha=0.7, axis="x", which="minor") fig.tight_layout() if return_fig: return fig, ax return None