import numpy as np
import openmdao.api as om
[docs]
class FarmAeroTemplate(om.ExplicitComponent):
"""
Template component for using a farm aerodynamics model.
A farm aerodynamics component, based on this template, will compute the
aerodynamics for a farm with some layout and yaw configuration.
Options
-------
modeling_options : dict
a modeling options dictionary
Inputs
------
x_turbines : np.ndarray
a 1D numpy array indicating the x-dimension locations of the turbines,
with length `N_turbines`
y_turbines : np.ndarray
a 1D numpy array indicating the y-dimension locations of the turbines,
with length `N_turbines`
yaw_turbines : np.ndarray
a numpy array indicating the yaw angle to drive each turbine to with
respect to the ambient wind direction, with length `N_turbines`
Outputs
-------
None
"""
[docs]
def initialize(self):
"""Initialization of OM component."""
self.options.declare("modeling_options")
[docs]
def setup(self):
"""Setup of OM component."""
# load modeling options
self.modeling_options = self.options["modeling_options"]
self.N_turbines = self.modeling_options["farm"]["N_turbines"]
# set up inputs and outputs for farm layout
self.add_input("x_turbines", np.zeros((self.N_turbines,)), units="m")
self.add_input("y_turbines", np.zeros((self.N_turbines,)), units="m")
self.add_input(
"yaw_turbines",
np.zeros((self.N_turbines,)),
units="deg",
)
[docs]
def compute(self, inputs, outputs):
"""
Computation for the OM component.
For a template class this is not implemented and raises an error!
"""
#############################################
# #
# IMPLEMENT THE AERODYNAMICS COMPONENT HERE #
# #
#############################################
raise NotImplementedError(
"This is an abstract class for a derived class to implement!"
)
[docs]
class BatchFarmPowerTemplate(FarmAeroTemplate):
"""
Template component for computing power using a farm aerodynamics model.
A farm power component, based on this template, will compute the power and
thrust for a farm composed of a given rotor type.
Options
-------
modeling_options : dict
a modeling options dictionary (inherited from `FarmAeroTemplate`)
wind_query : floris.wind_data.WindRose
a WindQuery objects that specifies the wind conditions that are to be
computed
Inputs
------
x_turbines : np.ndarray
a 1D numpy array indicating the x-dimension locations of the turbines,
with length `N_turbines` (inherited from `FarmAeroTemplate`)
y_turbines : np.ndarray
a 1D numpy array indicating the y-dimension locations of the turbines,
with length `N_turbines` (inherited from `FarmAeroTemplate`)
yaw_turbines : np.ndarray
a numpy array indicating the yaw angle to drive each turbine to with
respect to the ambient wind direction, with length `N_turbines`
(inherited from `FarmAeroTemplate`)
Outputs
-------
power_farm : np.ndarray
an array of the farm power for each of the wind conditions that have
been queried
power_turbines : np.ndarray
an array of the farm power for each of the turbines in the farm across
all of the conditions that have been queried on the wind rose
(`N_turbines`, `N_wind_conditions`)
thrust_turbines : np.ndarray
an array of the wind turbine thrust for each of the turbines in the farm
across all of the conditions that have been queried on the wind rose
(`N_turbines`, `N_wind_conditions`)
"""
[docs]
def initialize(self):
"""Initialization of OM component."""
super().initialize()
# farm power wind conditions query (not necessarily a full wind rose)
self.options.declare("wind_query")
[docs]
def setup(self):
"""Setup of OM component."""
super().setup()
# unpack wind query object
self.wind_query = self.options["wind_query"]
self.directions_wind = self.options["wind_query"].get_directions()
self.speeds_wind = self.options["wind_query"].get_speeds()
if self.options["wind_query"].get_TIs() is None:
self.options["wind_query"].set_TI_using_IEC_method()
self.TIs_wind = self.options["wind_query"].get_TIs()
self.N_wind_conditions = self.options["wind_query"].N_conditions
# add the outputs we want for a batched power analysis:
# - farm and turbine powers
# - turbine thrusts
self.add_output(
"power_farm",
np.zeros((self.N_wind_conditions,)),
units="W",
)
self.add_output(
"power_turbines",
np.zeros((self.N_turbines, self.N_wind_conditions)),
units="W",
)
self.add_output(
"thrust_turbines",
np.zeros((self.N_turbines, self.N_wind_conditions)),
units="N",
)
[docs]
def setup_partials(self):
"""Derivative setup for OM component."""
# the default (but not preferred!) derivatives are FDM
self.declare_partials("*", "*", method="fd")
[docs]
def compute(self, inputs, outputs):
"""
Computation for the OM component.
For a template class this is not implemented and raises an error!
"""
super().compute(inputs, outputs)
raise NotImplementedError(
"This is an abstract class for a derived class to implement!"
)
# the following should be set
outputs["power_farm"] = np.zeros((self.N_wind_conditions,))
outputs["power_turbines"] = np.zeros((self.N_turbines, self.N_wind_conditions))
outputs["thrust_turbines"] = np.zeros((self.N_turbines, self.N_wind_conditions))
[docs]
class FarmAEPTemplate(FarmAeroTemplate):
"""
A template component for computing power using a farm aerodynamics model.
A farm power component, based on this template, will compute the power and
thrust for a farm composed of a given rotor type.
Options
-------
modeling_options : dict
a modeling options dictionary (inherited from FarmAeroTemplate)
wind_rose : floris.wind_data.WindRose
a FLORIS WindRose object that fully specifies the wind conditions on
which a farm is to be evaluated
Inputs
------
x_turbines : np.ndarray
a 1D numpy array indicating the x-dimension locations of the turbines,
with length `N_turbines` (inherited from `FarmAeroTemplate`)
y_turbines : np.ndarray
a 1D numpy array indicating the y-dimension locations of the turbines,
with length `N_turbines` (inherited from `FarmAeroTemplate`)
yaw_turbines : np.ndarray
a numpy array indicating the yaw angle to drive each turbine to with
respect to the ambient wind direction, with length `N_turbines`
(inherited from `FarmAeroTemplate`)
Outputs
-------
AEP_farm : float
the AEP of the farm given by the analysis
power_farm : np.ndarray
an array of the farm power for each of the wind conditions that have
been queried
power_turbines : np.ndarray
an array of the farm power for each of the turbines in the farm across
all of the conditions that have been queried on the wind rose
(`N_turbines`, `N_wind_conditions`)
thrust_turbines : np.ndarray
an array of the wind turbine thrust for each of the turbines in the farm
across all of the conditions that have been queried on the wind rose
(`N_turbines`, `N_wind_conditions`)
"""
[docs]
def initialize(self):
"""Initialization of OM component."""
super().initialize()
# wind conditions for AEP analysis are a FLORIS WindRose
self.options.declare("wind_rose") # FLORIS WindRose object
[docs]
def setup(self):
"""Setup of OM component."""
super().setup()
# unpack FLORIS wind data object
self.wind_rose = self.options["wind_rose"]
self.directions_wind, self.speeds_wind, self.TIs_wind, self.pmf_wind, _, _ = (
self.wind_rose.unpack()
)
self.N_wind_conditions = len(self.pmf_wind)
# add the outputs we want for an AEP analysis:
# - AEP estimate
# - farm and turbine powers
# - turbine thrusts
self.add_output(
"AEP_farm",
0.0,
units="W*h",
)
self.add_output(
"power_farm",
np.zeros((self.N_wind_conditions,)),
units="W",
)
self.add_output(
"power_turbines",
np.zeros((self.N_turbines, self.N_wind_conditions)),
units="W",
)
self.add_output(
"thrust_turbines",
np.zeros((self.N_turbines, self.N_wind_conditions)),
units="N",
)
# ... more outputs can be added here
[docs]
def setup_partials(self):
"""Derivative setup for OM component."""
# the default (but not preferred!) derivatives are FDM
self.declare_partials("*", "*", method="fd")
[docs]
def compute(self, inputs, outputs):
"""
Computation for the OM component.
For a template class this is not implemented and raises an error!
"""
#############################################
# #
# IMPLEMENT THE AERODYNAMICS COMPONENT HERE #
# #
#############################################
raise NotImplementedError(
"This is an abstract class for a derived class to implement!"
)
# the following should be set
outputs["AEP_farm"] = 0.0
outputs["power_farm"] = np.zeros((self.N_wind_conditions,))
outputs["power_turbines"] = np.zeros((self.N_turbines, self.N_wind_conditions))
outputs["thrust_turbines"] = np.zeros((self.N_turbines, self.N_wind_conditions))