from __future__ import annotations
from pathlib import Path
import attrs
import matplotlib.pyplot as plt
import numpy as np
from attrs import define, field
from floris.core.turbine.operation_models import POWER_SETPOINT_DEFAULT
from floris.core.turbine.turbine import (
power,
thrust_coefficient,
Turbine,
)
from floris.type_dec import convert_to_path, NDArrayFloat
from floris.utilities import (
load_yaml,
round_nearest,
round_nearest_2_or_5,
)
INTERNAL_LIBRARY = Path(__file__).parent
DEFAULT_WIND_SPEEDS = np.linspace(0, 40, 81)
[docs]
@define(auto_attribs=True)
class TurbineInterface:
turbine: Turbine = field(validator=attrs.validators.instance_of(Turbine))
[docs]
@classmethod
def from_library(cls, library_path: str | Path, file_name: str):
"""Loads the turbine definition from a YAML configuration file located in either the
internal turbine library ``floris/floris/turbine_library/``, or a user-specified location.
Args:
library_path (:obj:`str` | :obj:`pathlib.Path`): The location of the turbine library;
use "internal" to use the FLORIS-provided library.
file_name (:obj:`str` | :obj:`pathlib.Path`): The name of the configuration file.
Returns:
(TurbineInterface): Creates a new ``TurbineInterface`` object.
"""
# Use the pre-mapped internal turbine library or validate the user's library
if library_path == "internal":
library_path = INTERNAL_LIBRARY
else:
library_path = convert_to_path(library_path)
# Add in the library specification if needed, and load from dict
turb_dict = load_yaml(library_path / file_name)
return cls(turbine=Turbine.from_dict(turb_dict))
[docs]
@classmethod
def from_yaml(cls, file_path: str | Path):
"""Loads the turbine definition from a YAML configuration file.
Args:
file_path : str | Path
The full path and file name of the turbine configuration file.
Returns:
(TurbineInterface): Creates a new ``TurbineInterface`` object.
"""
file_path = Path(file_path).resolve()
# Add in the library specification if needed, and load from dict
turb_dict = load_yaml(file_path)
return cls(turbine=Turbine.from_dict(turb_dict))
[docs]
@classmethod
def from_turbine_dict(cls, config_dict: dict):
"""Loads the turbine definition from a dictionary.
Args:
config_dict : dict
The ``Turbine`` configuration dictionary.
Returns:
(`TurbineInterface`): Returns a ``TurbineInterface`` object.
"""
return cls(turbine=Turbine.from_dict(config_dict))
[docs]
def power_curve(
self,
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
) -> tuple[NDArrayFloat, NDArrayFloat] | tuple[NDArrayFloat, dict[tuple, NDArrayFloat]]:
"""Produces a plot-ready power curve for the turbine for wind speed vs power (MW), assuming
no tilt or yaw effects.
Args:
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
Returns:
(tuple[NDArrayFloat, NDArrayFloat] | tuple[NDArrayFloat, dict[tuple, NDArrayFloat]]):
Returns the wind speed array and the power array, or the wind speed array and a
dictionary of the multidimensional parameters and their associated power arrays.
"""
shape = (wind_speeds.size, 1)
if self.turbine.multi_dimensional_cp_ct:
power_mw = {
k: power(
velocities=wind_speeds.reshape(shape),
turbulence_intensities=np.zeros(shape),
air_density=np.full(shape, v["ref_air_density"]),
power_functions={self.turbine.turbine_type: self.turbine.power_function},
yaw_angles=np.zeros(shape),
tilt_angles=np.full(shape, v["ref_tilt"]),
power_setpoints=np.full(shape, POWER_SETPOINT_DEFAULT),
awc_modes=np.full(shape, ["baseline"]),
awc_amplitudes=np.zeros(shape),
tilt_interps={self.turbine.turbine_type: self.turbine.tilt_interp},
turbine_type_map=np.full(shape, self.turbine.turbine_type),
turbine_power_thrust_tables={self.turbine.turbine_type: v},
).flatten() / 1e6
for k,v in self.turbine.power_thrust_table.items()
}
else:
power_mw = power(
velocities=wind_speeds.reshape(shape),
turbulence_intensities=np.zeros(shape),
air_density=np.full(shape, self.turbine.power_thrust_table["ref_air_density"]),
power_functions={self.turbine.turbine_type: self.turbine.power_function},
yaw_angles=np.zeros(shape),
tilt_angles=np.full(shape, self.turbine.power_thrust_table["ref_tilt"]),
power_setpoints=np.full(shape, POWER_SETPOINT_DEFAULT),
awc_modes=np.full(shape, ["baseline"]),
awc_amplitudes=np.zeros(shape),
tilt_interps={self.turbine.turbine_type: self.turbine.tilt_interp},
turbine_type_map=np.full(shape, self.turbine.turbine_type),
turbine_power_thrust_tables={
self.turbine.turbine_type: self.turbine.power_thrust_table
},
).flatten() / 1e6
return wind_speeds, power_mw
[docs]
def thrust_coefficient_curve(
self,
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
) -> tuple[NDArrayFloat, NDArrayFloat]:
"""Produces a plot-ready thrust curve for the turbine for wind speed vs thrust coefficient
assuming no tilt or yaw effects.
Args:
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
Returns:
tuple[NDArrayFloat, NDArrayFloat]
Returns the wind speed array and the thrust coefficient array.
"""
shape = (wind_speeds.size, 1)
if self.turbine.multi_dimensional_cp_ct:
ct_curve = {
k: thrust_coefficient(
velocities=wind_speeds.reshape(shape),
turbulence_intensities=np.zeros(shape),
air_density=np.full(shape, v["ref_air_density"]),
yaw_angles=np.zeros(shape),
tilt_angles=np.full(shape, v["ref_tilt"]),
power_setpoints=np.full(shape, POWER_SETPOINT_DEFAULT),
awc_modes=np.full(shape, ["baseline"]),
awc_amplitudes=np.zeros(shape),
thrust_coefficient_functions={
self.turbine.turbine_type: self.turbine.thrust_coefficient_function
},
tilt_interps={self.turbine.turbine_type: self.turbine.tilt_interp},
correct_cp_ct_for_tilt=np.zeros(shape, dtype=bool),
turbine_type_map=np.full(shape, self.turbine.turbine_type),
turbine_power_thrust_tables={self.turbine.turbine_type: v},
).flatten()
for k,v in self.turbine.power_thrust_table.items()
}
else:
ct_curve = thrust_coefficient(
velocities=wind_speeds.reshape(shape),
turbulence_intensities=np.zeros(shape),
air_density=np.full(shape, self.turbine.power_thrust_table["ref_air_density"]),
yaw_angles=np.zeros(shape),
tilt_angles=np.full(shape, self.turbine.power_thrust_table["ref_tilt"]),
power_setpoints=np.full(shape, POWER_SETPOINT_DEFAULT),
awc_modes=np.full(shape, ["baseline"]),
awc_amplitudes=np.zeros(shape),
thrust_coefficient_functions={
self.turbine.turbine_type: self.turbine.thrust_coefficient_function
},
tilt_interps={self.turbine.turbine_type: self.turbine.tilt_interp},
correct_cp_ct_for_tilt=np.zeros(shape, dtype=bool),
turbine_type_map=np.full(shape, self.turbine.turbine_type),
turbine_power_thrust_tables={
self.turbine.turbine_type: self.turbine.power_thrust_table
},
).flatten()
return wind_speeds, ct_curve
[docs]
def plot_power_curve(
self,
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
fig_kwargs: dict | None = None,
plot_kwargs: dict | None = None,
legend_kwargs: dict | None = None,
return_fig: bool = False
) -> None | tuple[plt.Figure, plt.Axes]:
"""Plots the power curve for a given set of wind speeds.
Args:
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s.
Defaults to 0 m/s -> 40 m/s, every 0.5 m/s.
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
plot_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.plot()``.
Defaults to None.
legend_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.legend()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
Returns:
None | tuple[plt.Figure, plt.Axes]: None, if :py:attr:`return_fig` is False, otherwise
a tuple of the Figure and Axes objects are returned.
"""
wind_speeds, power_mw = self.power_curve(wind_speeds=wind_speeds)
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
plot_kwargs = {} if plot_kwargs is None else plot_kwargs
legend_kwargs = {} if legend_kwargs is None else legend_kwargs
# Set the figure defaults if none are provided
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (4, 3))
fig = plt.figure(**fig_kwargs)
ax = fig.add_subplot(111)
min_windspeed = 0
max_windspeed = max(wind_speeds)
min_power = 0
max_power = 0
if isinstance(power_mw, dict):
for key, _power_mw in power_mw.items():
max_power = max(max_power, *_power_mw)
_cond = "; ".join((f"{c}: {k}" for c, k in zip(self.turbine.condition_keys, key)))
label = f"{self.turbine.turbine_type} - {_cond}"
ax.plot(wind_speeds, _power_mw, label=label, **plot_kwargs)
else:
max_power = max(power_mw)
ax.plot(wind_speeds, power_mw, label=self.turbine.turbine_type, **plot_kwargs)
ax.grid()
ax.set_axisbelow(True)
ax.legend(**legend_kwargs)
max_power = round_nearest_2_or_5(max_power)
ax.set_xlim(min_windspeed, max_windspeed)
ax.set_ylim(min_power, max_power)
ax.set_xlabel("Wind Speed (m/s)")
ax.set_ylabel("Power (MW)")
if return_fig:
return fig, ax
fig.tight_layout()
[docs]
def plot_thrust_coefficient_curve(
self,
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
fig_kwargs: dict | None = None,
plot_kwargs: dict | None = None,
legend_kwargs: dict | None = None,
return_fig: bool = False
) -> None | tuple[plt.Figure, plt.Axes]:
"""Plots the thrust coefficient curve for a given set of wind speeds.
Args:
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
plot_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.plot()``.
Defaults to None.
legend_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.legend()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
Returns:
None | tuple[plt.Figure, plt.Axes]: None, if :py:attr:`return_fig` is False, otherwise
a tuple of the Figure and Axes objects are returned.
"""
wind_speeds, thrust = self.thrust_coefficient_curve(wind_speeds=wind_speeds)
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
plot_kwargs = {} if plot_kwargs is None else plot_kwargs
legend_kwargs = {} if legend_kwargs is None else legend_kwargs
# Set the figure defaults if none are provided
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (4, 3))
fig = plt.figure(**fig_kwargs)
ax = fig.add_subplot(111)
min_windspeed = 0
max_thrust = 0
max_windspeed = max(wind_speeds)
if isinstance(thrust, dict):
for key, _thrust in thrust.items():
max_thrust = max(max_thrust, *_thrust)
_cond = "; ".join((f"{c}: {k}" for c, k in zip(self.turbine.condition_keys, key)))
label = f"{self.turbine.turbine_type} - {_cond}"
ax.plot(wind_speeds, _thrust, label=label, **plot_kwargs)
else:
max_thrust = max(thrust)
ax.plot(wind_speeds, thrust, label=self.turbine.turbine_type, **plot_kwargs)
ax.grid()
ax.set_axisbelow(True)
ax.legend(**legend_kwargs)
ax.set_xlim(min_windspeed, max_windspeed)
ax.set_ylim(0, round_nearest(max_thrust * 100, base=10) / 100)
ax.set_xlabel("Wind Speed (m/s)")
ax.set_ylabel("Thrust Coefficient")
if return_fig:
return fig, ax
fig.tight_layout()
[docs]
@define(auto_attribs=True)
class TurbineLibrary:
turbine_map: dict[str: TurbineInterface] = field(factory=dict)
power_curves: dict[str, tuple[NDArrayFloat, NDArrayFloat]] = field(factory=dict)
thrust_coefficient_curves: dict[str, tuple[NDArrayFloat, NDArrayFloat]] = field(factory=dict)
[docs]
def load_internal_library(self, which: list[str] = [], exclude: list[str] = []) -> None:
"""Loads all of the turbine configurations from ``floris/floris/turbine_libary``,
except any turbines defined in :py:attr:`exclude`.
Args:
which (list[str], optional): A list of which file names to include from loading.
Defaults to [].
exclude (list[str], optional): A list of file names to exclude from loading.
Defaults to [].
"""
include = [el for el in INTERNAL_LIBRARY.iterdir() if el.suffix in (".yaml", ".yml")]
which = [INTERNAL_LIBRARY / el for el in which] if which != [] else include
exclude = [INTERNAL_LIBRARY / el for el in exclude]
include = set(which).intersection(include).difference(exclude)
for fn in include:
turbine_dict = load_yaml(fn)
self.turbine_map.update({
turbine_dict["turbine_type"]: TurbineInterface.from_turbine_dict(turbine_dict)
})
[docs]
def load_external_library(
self,
library_path: str | Path,
which: list[str] = [],
exclude: list[str] = [],
) -> None:
"""Loads all the turbine configurations from :py:attr:`library_path`, except the file names
defined in :py:attr:`exclude`, and adds each to ``turbine_map`` via a dictionary
update.
Args:
library_path : str | Path
The external turbine library that should be used for loading the turbines.
which (list[str], optional): A list of which file names to include from loading.
Defaults to [].
exclude (list[str], optional): A list of file names to exclude from loading.
Defaults to [].
"""
library_path = Path(library_path).resolve()
include = [el for el in library_path.iterdir() if el.suffix in (".yaml", ".yml")]
which = [library_path / el for el in which] if which != [] else include
exclude = [library_path / el for el in exclude]
include = set(which).intersection(include).difference(exclude)
for fn in include:
turbine_dict = load_yaml(fn)
self.turbine_map.update({
turbine_dict["turbine_type"]: TurbineInterface.from_turbine_dict(turbine_dict)
})
[docs]
def compute_power_curves(
self,
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
) -> None:
"""Computes the power curves for each turbine in ``turbine_map`` and sets the
``power_curves`` attribute.
Args:
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
"""
self.power_curves = {
name: t.power_curve(wind_speeds) for name, t in self.turbine_map.items()
}
[docs]
def compute_thrust_coefficient_curves(
self,
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
) -> None:
"""Computes the thrust curves for each turbine in ``turbine_map`` and sets the
``thrust_coefficient_curves`` attribute.
Args:
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
"""
self.thrust_coefficient_curves = {
name: t.thrust_coefficient_curve(wind_speeds) for name, t in self.turbine_map.items()
}
[docs]
def plot_power_curves(
self,
fig: plt.Figure | None = None,
ax: plt.Axes | None = None,
which: list[str] = [],
exclude: list[str] = [],
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
fig_kwargs: dict | None = None,
plot_kwargs: dict | None = None,
legend_kwargs: dict | None = None,
return_fig: bool = False,
show: bool = False,
) -> None | tuple[plt.Figure, plt.Axes]:
"""Plots each power curve in ``turbine_map`` in a single plot.
Args:
fig (plt.figure, optional): A pre-made figure where the plot should exist.
ax (plt.Axes, optional): A pre-initialized axes object that should be used for the plot.
which (list[str], optional): A list of which turbine types/names to include. Defaults to
[].
exclude (list[str], optional): A list of turbine types/names names to exclude. Defaults
to [].
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
plot_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.plot()``.
Defaults to None.
legend_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.legend()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
show (bool, optional): Indicator if the figure should be automatically displayed.
Defaults to False.
Returns:
None | tuple[plt.Figure, plt.Axes]: None, if :py:attr:`return_fig` is False, otherwise
a tuple of the Figure and Axes objects are returned.
"""
if self.power_curves == {} or wind_speeds is not None:
self.compute_power_curves(wind_speeds=wind_speeds)
which = [*self.turbine_map] if which == [] else which
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
plot_kwargs = {} if plot_kwargs is None else plot_kwargs
legend_kwargs = {} if legend_kwargs is None else legend_kwargs
# Set the figure defaults if none are provided
if fig is None:
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (4, 3))
fig = plt.figure(**fig_kwargs)
if ax is None:
ax = fig.add_subplot(111)
min_windspeed = 0
max_windspeed = 0
min_power = 0
max_power = 0
for name, (ws, p) in self.power_curves.items():
if name in exclude or name not in which:
continue
if isinstance(p, dict):
max_windspeed = max(ws.max(), max_windspeed)
for k, _p in p.items():
max_power = max(_p.max(), max_power)
label = f"{name} - {k}"
ax.plot(ws, _p, label=label, linestyle="--", **plot_kwargs)
else:
max_power = max(p.max(), max_power)
max_windspeed = max(ws.max(), max_windspeed)
ax.plot(ws, p, label=name, **plot_kwargs)
ax.grid()
ax.set_axisbelow(True)
ax.legend(**legend_kwargs)
max_power = round_nearest(max_power, base=5)
ax.set_xlim(min_windspeed, max_windspeed)
ax.set_ylim(min_power, max_power)
ax.set_xlabel("Wind Speed (m/s)")
ax.set_ylabel("Power (MW)")
if return_fig:
return fig, ax
if show:
fig.tight_layout()
[docs]
def plot_thrust_coefficient_curves(
self,
fig: plt.Figure | None = None,
ax: plt.Axes | None = None,
which: list[str] = [],
exclude: list[str] = [],
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
fig_kwargs: dict | None = None,
plot_kwargs: dict | None = None,
legend_kwargs: dict | None = None,
return_fig: bool = False,
show: bool = False,
) -> None | tuple[plt.Figure, plt.Axes]:
"""Plots each thrust coefficient curve in ``turbine_map`` in a single plot.
Args:
fig (plt.figure, optional): A pre-made figure where the plot should exist.
ax (plt.Axes, optional): A pre-initialized axes object that should be used for the plot.
which (list[str], optional): A list of which turbine types/names to include. Defaults to
[].
exclude (list[str], optional): A list of turbine types/names names to exclude. Defaults
to [].
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
plot_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.plot()``.
Defaults to None.
plot_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.legend()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
show (bool, optional): Indicator if the figure should be automatically displayed.
Defaults to False.
Returns:
None | tuple[plt.Figure, plt.Axes]: None, if :py:attr:`return_fig` is False, otherwise
a tuple of the Figure and Axes objects are returned.
"""
if self.thrust_coefficient_curves == {} or wind_speeds is None:
self.compute_thrust_coefficient_curves(wind_speeds=wind_speeds)
which = [*self.turbine_map] if which == [] else which
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
plot_kwargs = {} if plot_kwargs is None else plot_kwargs
legend_kwargs = {} if legend_kwargs is None else legend_kwargs
# Set the figure defaults if none are provided
if fig is None:
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (4, 3))
fig = plt.figure(**fig_kwargs)
if ax is None:
ax = fig.add_subplot(111)
min_windspeed = 0
max_windspeed = 0
max_thrust = 0
for name, (ws, t) in self.thrust_coefficient_curves.items():
if name in exclude or name not in which:
continue
if isinstance(t, dict):
max_windspeed = max(ws.max(), max_windspeed)
for k, _t in t.items():
max_thrust = max(_t.max(), max_thrust)
label = f"{name} - {k}"
ax.plot(ws, _t, label=label, linestyle="--", **plot_kwargs)
else:
max_windspeed = max(ws.max(), max_windspeed)
max_thrust = max(t.max(), max_thrust)
ax.plot(ws, t, label=name, **plot_kwargs)
ax.grid()
ax.set_axisbelow(True)
ax.legend(**legend_kwargs)
ax.set_xlim(min_windspeed, max_windspeed)
ax.set_ylim(0, round_nearest(max_thrust * 100, base=10) / 100)
ax.set_xlabel("Wind Speed (m/s)")
ax.set_ylabel("Thrust Coefficient")
if return_fig:
return fig, ax
if show:
fig.tight_layout()
[docs]
def plot_rotor_diameters(
self,
fig: plt.Figure | None = None,
ax: plt.Axes | None = None,
which: list[str] = [],
exclude: list[str] = [],
fig_kwargs: dict | None = None,
bar_kwargs: dict | None = None,
return_fig: bool = False,
show: bool = False,
) -> None | tuple[plt.Figure, plt.Axes]:
"""Plots a bar chart of rotor diameters for each turbine in ``turbine_map``.
Args:
fig (plt.figure, optional): A pre-made figure where the plot should exist.
ax (plt.Axes, optional): A pre-initialized axes object that should be used for the plot.
which (list[str], optional): A list of which turbine types/names to include. Defaults to
[].
exclude (list[str], optional): A list of turbine types/names names to exclude. Defaults
to [].
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
bar_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.bar()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
show (bool, optional): Indicator if the figure should be automatically displayed.
Defaults to False.
Returns:
None | tuple[plt.Figure, plt.Axes]: None, if :py:attr:`return_fig` is False, otherwise
a tuple of the Figure and Axes objects are returned.
"""
which = [*self.turbine_map] if which == [] else which
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
bar_kwargs = {} if bar_kwargs is None else bar_kwargs
# Set the figure defaults if none are provided
if fig is None:
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (4, 3))
fig = plt.figure(**fig_kwargs)
if ax is None:
ax = fig.add_subplot(111)
subset_map = {
name: t for name, t in self.turbine_map.items()
if name not in exclude or name in which
}
x = np.arange(len(subset_map))
y = [ti.turbine.rotor_diameter for ti in subset_map.values()]
ix_sort = np.argsort(y)
y_sorted = np.array(y)[ix_sort]
ax.bar(x, y_sorted, **bar_kwargs)
ax.grid(axis="y")
ax.set_axisbelow(True)
ax.set_xlim(-0.5, len(x) - 0.5)
ax.set_ylim(0, round_nearest(max(y) / 10, base=5) * 10)
ax.set_xticks(x)
ax.set_xticklabels(np.array([*subset_map])[ix_sort], rotation=30, ha="right")
ax.set_ylabel("Rotor Diameter (m)")
if return_fig:
return fig, ax
if show:
fig.tight_layout()
[docs]
def plot_hub_heights(
self,
fig: plt.Figure | None = None,
ax: plt.Axes | None = None,
which: list[str] = [],
exclude: list[str] = [],
fig_kwargs: dict | None = None,
bar_kwargs: dict | None = None,
return_fig: bool = False,
show: bool = False,
) -> None | tuple[plt.Figure, plt.Axes]:
"""Plots a bar chart of hub heights for each turbine in ``turbine_map``.
Args:
fig (plt.figure, optional): A pre-made figure where the plot should exist.
ax (plt.Axes, optional): A pre-initialized axes object that should be used for the plot.
which (list[str], optional): A list of which turbine types/names to include. Defaults to
[].
exclude (list[str], optional): A list of turbine types/names names to exclude. Defaults
to [].
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
bar_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.bar()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
show (bool, optional): Indicator if the figure should be automatically displayed.
Defaults to False.
Returns:
None | tuple[plt.Figure, plt.Axes]: None, if :py:attr:`return_fig` is False, otherwise
a tuple of the Figure and Axes objects are returned.
"""
which = [*self.turbine_map] if which == [] else which
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
bar_kwargs = {} if bar_kwargs is None else bar_kwargs
# Set the figure defaults if none are provided
if fig is None:
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (4, 3))
fig = plt.figure(**fig_kwargs)
if ax is None:
ax = fig.add_subplot(111)
subset_map = {
name: t for name, t in self.turbine_map.items()
if name not in exclude or name in which
}
x = np.arange(len(subset_map))
y = [ti.turbine.hub_height for ti in subset_map.values()]
ix_sort = np.argsort(y)
y_sorted = np.array(y)[ix_sort]
ax.bar(x, y_sorted, **bar_kwargs)
ax.grid(axis="y")
ax.set_axisbelow(True)
ax.set_xlim(-0.5, len(x) - 0.5)
ax.set_ylim(0, round_nearest(max(y) / 10, base=5) * 10)
ax.set_xticks(x)
ax.set_xticklabels(np.array([*subset_map])[ix_sort], rotation=30, ha="right")
ax.set_ylabel("Hub Height (m)")
if return_fig:
return fig, ax
if show:
fig.tight_layout()
[docs]
def plot_comparison(
self,
which: list[str] = [],
exclude: list[str] = [],
wind_speeds: NDArrayFloat = DEFAULT_WIND_SPEEDS,
fig_kwargs: dict | None = None,
plot_kwargs: dict | None = None,
bar_kwargs: dict | None = None,
legend_kwargs: dict | None = None,
return_fig: bool = False
) -> None | tuple[plt.Figure, list[plt.Axes]]:
"""Plots each thrust curve in ``turbine_map`` in a single plot.
Args:
which (list[str], optional): A list of which turbine types/names to include. Defaults to
[].
exclude (list[str], optional): A list of turbine types/names names to exclude. Defaults
to [].
wind_speeds (NDArrayFloat, optional): A 1-D array of wind speeds, in m/s. Defaults to
0 m/s -> 40 m/s, every 0.5 m/s.
fig_kwargs (dict, optional): Any keywords arguments to be passed to ``plt.Figure()``.
Defaults to None.
plot_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.plot()``.
Defaults to None.
bar_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.bar()``.
Defaults to None.
legend_kwargs (dict, optional): Any keyword arguments to be passed to ``plt.legend()``.
Defaults to None.
return_fig (bool, optional): Indicator if the ``Figure`` and ``Axes`` objects should be
returned. Defaults to False.
Returns:
None | tuple[plt.Figure, list[plt.Axes]]: None, if :py:attr:`return_fig` is False,
otherwise a tuple of the Figure and Axes objects are returned.
"""
# Initialize kwargs if None
fig_kwargs = {} if fig_kwargs is None else fig_kwargs
plot_kwargs = {} if plot_kwargs is None else plot_kwargs
bar_kwargs = {} if bar_kwargs is None else bar_kwargs
legend_kwargs = {} if legend_kwargs is None else legend_kwargs
# Set the figure defaults if none are provided
fig_kwargs.setdefault("dpi", 200)
fig_kwargs.setdefault("figsize", (6, 5))
legend_kwargs.setdefault("fontsize", 6)
fig = plt.figure(**fig_kwargs)
ax1 = fig.add_subplot(321)
ax2 = fig.add_subplot(322)
ax3 = fig.add_subplot(323)
ax4 = fig.add_subplot(324)
ax_list = [ax1, ax2, ax3, ax4]
self.plot_power_curves(
fig,
ax1,
which=which,
exclude=exclude,
wind_speeds=wind_speeds,
plot_kwargs=plot_kwargs,
)
self.plot_thrust_coefficient_curves(
fig,
ax3,
which=which,
exclude=exclude,
wind_speeds=wind_speeds,
plot_kwargs=plot_kwargs,
)
self.plot_rotor_diameters(fig, ax2, which=which, exclude=exclude, bar_kwargs=bar_kwargs)
self.plot_hub_heights(fig, ax4, which=which, bar_kwargs=bar_kwargs)
for ax in ax_list:
ax.tick_params(axis='both', which='major', labelsize=7)
ax.xaxis.label.set_size(7)
ax.yaxis.label.set_size(8)
for ax in (ax1, ax3):
ax.legend(**legend_kwargs)
if return_fig:
return fig, ax_list
fig.tight_layout()