Skip to content

Commit

Permalink
fix: define default units for fields
Browse files Browse the repository at this point in the history
Use default units in the plots. This removes some of the hard-coded
strings, therefore mitigating typos of units.

Fixes #27
  • Loading branch information
samuelwnaylor committed Sep 23, 2024
1 parent 5fee858 commit d6d1291
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 38 deletions.
Empty file added tests/plots/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions tests/plots/test_scada_funcs_plots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import re

import pytest

from wind_up.plots import scada_funcs_plots


class TestAxisLabelFromFieldName:
@staticmethod
@pytest.mark.parametrize(
"field_name_unsupported", ["UnsupportedFieldName", "test_UnsupportedFieldName", "raw_UnsupportedFieldName"]
)
def test_unsupported_field_name(field_name_unsupported: str) -> None:
fname_unsupported_lean = re.sub(r"^.*?_", "", field_name_unsupported)
msg = (
f"Failed to construct axis label for field '{field_name_unsupported}' "
f"because {fname_unsupported_lean} does not have a default unit defined"
)

with pytest.raises(ValueError, match=msg):
scada_funcs_plots._axis_label_from_field_name(field_name=field_name_unsupported) # noqa: SLF001

@staticmethod
@pytest.mark.parametrize(
("field_name", "expected"),
[
("ActivePowerMean", "ActivePowerMean [kW]"),
("test_ActivePowerMean", "test_ActivePowerMean [kW]"),
("raw_ActivePowerMean", "raw_ActivePowerMean [kW]"),
("WindSpeedMean", "WindSpeedMean [m/s]"),
("YawAngleMean", "YawAngleMean [deg]"),
("PitchAngleMean", "PitchAngleMean [deg]"),
("GenRpmMean", "GenRpmMean [RPM]"),
("AmbientTemp", "AmbientTemp [degC]"),
],
)
def test_supported_field_name(field_name: str, expected: str) -> None:
actual = scada_funcs_plots._axis_label_from_field_name(field_name=field_name) # noqa: SLF001
assert actual == expected
13 changes: 13 additions & 0 deletions wind_up/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ def all(cls: type["DataColumns"]) -> list[str]:
return [v for k, v in vars(cls).items() if not k.startswith("_") and k != "all"]


DATA_UNIT_DEFAULTS = {
DataColumns.active_power_mean: "kW",
DataColumns.active_power_sd: "kW",
DataColumns.wind_speed_mean: "m/s",
DataColumns.wind_speed_sd: "m/s",
DataColumns.yaw_angle_mean: "deg",
DataColumns.yaw_angle_min: "deg",
DataColumns.yaw_angle_max: "deg",
DataColumns.pitch_angle_mean: "deg",
DataColumns.gen_rpm_mean: "RPM",
DataColumns.ambient_temp: "degC",
}

HOURS_PER_YEAR = 8766
DEFAULT_AIR_DENSITY = 1.22

Expand Down
95 changes: 57 additions & 38 deletions wind_up/plots/scada_funcs_plots.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import matplotlib.pyplot as plt
import numpy as np
Expand All @@ -10,7 +10,7 @@
from tabulate import tabulate

from wind_up.backporting import strict_zip
from wind_up.constants import SCATTER_ALPHA, SCATTER_MARKERSCALE, SCATTER_S, DataColumns
from wind_up.constants import DATA_UNIT_DEFAULTS, SCATTER_ALPHA, SCATTER_MARKERSCALE, SCATTER_S, DataColumns
from wind_up.plots.misc_plots import bubble_plot

if TYPE_CHECKING:
Expand Down Expand Up @@ -103,13 +103,47 @@ def plot_ops_curves_per_ttype(cfg: WindUpConfig, df: pd.DataFrame, title_end: st
)


def _axis_label_from_field_name(field_name: str) -> str:
field_name_lean = field_name
for _prefix in ["test_", "raw_"]: # this is a list of known prefixes to column names used in some plots
field_name_lean = field_name_lean.replace(_prefix, "")

if field_name_lean not in DATA_UNIT_DEFAULTS:
msg = (
f"Failed to construct axis label for field '{field_name}' because {field_name_lean} does not have a "
"default unit defined"
)
raise ValueError(msg)

return f"{field_name} [{DATA_UNIT_DEFAULTS[field_name_lean]}]"


def _add_scatter_plot(ax: plt.Axes, scada_data: pd.DataFrame, x_col: str, y_col: str, **kwargs: Any) -> None: # noqa: ANN401
ax.scatter(x=scada_data[x_col], y=scada_data[y_col], s=SCATTER_S, alpha=SCATTER_ALPHA, **kwargs)
ax.set_xlabel(_axis_label_from_field_name(x_col))
ax.set_ylabel(_axis_label_from_field_name(y_col))
ax.grid()


def plot_ops_curves_one_ttype_or_wtg(df: pd.DataFrame, ttype_or_wtg: str, title_end: str, plot_cfg: PlotConfig) -> None:
"""
Plot turbine operating curves:
- Power Curve
- RPM and Pitch Angle vs. Power and Wind Speed
:param df: SCADA 10-minute data
:param ttype_or_wtg: Turbine type or name
:param title_end: appended to plot titles
:param plot_cfg: custom logic e.g. how to show/save plots
:return: None
"""
plt.figure()
plt.scatter(df[DataColumns.wind_speed_mean], df[DataColumns.active_power_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
plot_title = f"{ttype_or_wtg} power curve {title_end}"
plt.title(plot_title)
plt.xlabel(f"{DataColumns.wind_speed_mean} [m/s]")
plt.ylabel(f"{DataColumns.active_power_mean} [kW]")
plt.xlabel(_axis_label_from_field_name(DataColumns.wind_speed_mean))
plt.ylabel(_axis_label_from_field_name(DataColumns.active_power_mean))
plt.grid()
if plot_cfg.show_plots:
plt.show()
Expand All @@ -121,29 +155,18 @@ def plot_ops_curves_one_ttype_or_wtg(df: pd.DataFrame, ttype_or_wtg: str, title_

# plot rpm and pitch vs power and wind speed in a 2 by 2 grid
plt.figure(figsize=(12, 8))
plt.subplot(2, 2, 1)
plt.scatter(df[DataColumns.active_power_mean], df[DataColumns.gen_rpm_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
plt.xlabel(f"{DataColumns.active_power_mean} [kW]")
plt.ylabel(f"{DataColumns.gen_rpm_mean} [RPM]")
plt.grid()

plt.subplot(2, 2, 2)
plt.scatter(df[DataColumns.wind_speed_mean], df[DataColumns.gen_rpm_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
plt.xlabel(f"{DataColumns.wind_speed_mean} [m/s]")
plt.ylabel(f"{DataColumns.gen_rpm_mean} [RPM]")
plt.grid()
ax1 = plt.subplot(2, 2, 1)
_add_scatter_plot(ax=ax1, scada_data=df, x_col=DataColumns.active_power_mean, y_col=DataColumns.gen_rpm_mean)

plt.subplot(2, 2, 3)
plt.scatter(df[DataColumns.active_power_mean], df[DataColumns.pitch_angle_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
plt.xlabel(f"{DataColumns.active_power_mean} [kW]")
plt.ylabel(f"{DataColumns.pitch_angle_mean} [deg]")
plt.grid()
ax2 = plt.subplot(2, 2, 2)
_add_scatter_plot(ax=ax2, scada_data=df, x_col=DataColumns.wind_speed_mean, y_col=DataColumns.gen_rpm_mean)

plt.subplot(2, 2, 4)
plt.scatter(df[DataColumns.wind_speed_mean], df[DataColumns.pitch_angle_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
plt.xlabel(f"{DataColumns.wind_speed_mean} [m/s]")
plt.ylabel(f"{DataColumns.pitch_angle_mean} [deg]")
plt.grid()
ax3 = plt.subplot(2, 2, 3)
_add_scatter_plot(ax=ax3, scada_data=df, x_col=DataColumns.active_power_mean, y_col=DataColumns.pitch_angle_mean)

ax4 = plt.subplot(2, 2, 4)
_add_scatter_plot(ax=ax4, scada_data=df, x_col=DataColumns.wind_speed_mean, y_col=DataColumns.pitch_angle_mean)

plot_title = f"{ttype_or_wtg} ops curves, {title_end}"
plt.suptitle(plot_title)
Expand Down Expand Up @@ -256,14 +279,10 @@ def plot_toggle_ops_curves_one_ttype_or_wtg(

# plot rpm and pitch vs power and wind speed in a 2 by 2 grid
plt.figure(figsize=(12, 8))
plt.subplot(2, 2, 1)
plt.scatter(
df_off[pw_col],
df_off[rpm_col],
s=SCATTER_S,
alpha=SCATTER_ALPHA,
label=f"{toggle_name} OFF",
)
ax1 = plt.subplot(2, 2, 1)
_add_scatter_plot(ax=ax1, scada_data=df_off, x_col=pw_col, y_col=rpm_col, label=f"{toggle_name} OFF")
_add_scatter_plot(ax=ax1, scada_data=df_on, x_col=pw_col, y_col=rpm_col, label=f"{toggle_name} ON")

plt.scatter(
df_on[pw_col],
df_on[rpm_col],
Expand Down Expand Up @@ -367,16 +386,16 @@ def plot_filter_rpm_and_pt_curve_one_ttype_or_wtg(
y = [rpm_v_pw_curve["y_limit"].iloc[0], *list(rpm_v_pw_curve["y_limit"]), rpm_v_pw_curve["y_limit"].iloc[-1]]
plt.plot(x, y, color="red")
plt.xlabel("pw_clipped [kW]")
plt.ylabel(f"{DataColumns.gen_rpm_mean} [deg]")
plt.ylabel(_axis_label_from_field_name(DataColumns.gen_rpm_mean))
plt.grid()

plt.subplot(2, 2, 2)
plt.scatter(df[DataColumns.wind_speed_mean], df[DataColumns.gen_rpm_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
x = [rpm_v_ws_curve.index[0].left] + [x.mid for x in rpm_v_ws_curve.index] + [rpm_v_ws_curve.index[-1].right]
y = [rpm_v_ws_curve["y_limit"].iloc[0], *list(rpm_v_ws_curve["y_limit"]), rpm_v_ws_curve["y_limit"].iloc[-1]]
plt.plot(x, y, color="red")
plt.xlabel(f"{DataColumns.wind_speed_mean} [m/s]")
plt.ylabel(f"{DataColumns.gen_rpm_mean} [deg]")
plt.xlabel(_axis_label_from_field_name(DataColumns.wind_speed_mean))
plt.ylabel(_axis_label_from_field_name(DataColumns.gen_rpm_mean))
plt.grid()

plt.subplot(2, 2, 3)
Expand All @@ -385,16 +404,16 @@ def plot_filter_rpm_and_pt_curve_one_ttype_or_wtg(
y = [pt_v_pw_curve["y_limit"].iloc[0], *list(pt_v_pw_curve["y_limit"]), pt_v_pw_curve["y_limit"].iloc[-1]]
plt.plot(x, y, color="red")
plt.xlabel("pw_clipped [kW]")
plt.ylabel(f"{DataColumns.pitch_angle_mean} [deg]")
plt.ylabel(_axis_label_from_field_name(DataColumns.pitch_angle_mean))
plt.grid()

plt.subplot(2, 2, 4)
plt.scatter(df[DataColumns.wind_speed_mean], df[DataColumns.pitch_angle_mean], s=SCATTER_S, alpha=SCATTER_ALPHA)
x = [pt_v_ws_curve.index[0].left] + [x.mid for x in pt_v_ws_curve.index] + [pt_v_ws_curve.index[-1].right]
y = [pt_v_ws_curve["y_limit"].iloc[0], *list(pt_v_ws_curve["y_limit"]), pt_v_ws_curve["y_limit"].iloc[-1]]
plt.plot(x, y, color="red")
plt.xlabel(f"{DataColumns.wind_speed_mean} [m/s]")
plt.ylabel(f"{DataColumns.pitch_angle_mean} [deg]")
plt.xlabel(_axis_label_from_field_name(DataColumns.wind_speed_mean))
plt.ylabel(_axis_label_from_field_name(DataColumns.pitch_angle_mean))
plt.grid()

plot_title = f"{ttype_or_wtg} rpm and pitch curve filters"
Expand Down

0 comments on commit d6d1291

Please sign in to comment.