Skip to content

Commit

Permalink
Add capabilities to parallel_floris_model (#967)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulf81 authored Aug 22, 2024
1 parent 79fd2dc commit 1464208
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 23 deletions.
165 changes: 143 additions & 22 deletions floris/parallel_floris_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ def _get_turbine_powers_serial(fmodel_information, yaw_angles=None):
fmodel.run()
return (fmodel.get_turbine_powers(), fmodel.core.flow_field)

def _get_turbine_powers_serial_no_wake(fmodel_information, yaw_angles=None):
fmodel = FlorisModel(fmodel_information)
fmodel.set(yaw_angles=yaw_angles)
fmodel.run_no_wake()
return (fmodel.get_turbine_powers(), fmodel.core.flow_field)


def _optimize_yaw_angles_serial(
fmodel_information,
Expand Down Expand Up @@ -157,11 +163,52 @@ def set(
layout_x=None,
layout_y=None,
turbine_type=None,
turbine_library_path=None,
solver_settings=None,
heterogeneous_inflow_config=None,
wind_data=None,
yaw_angles=None,
power_setpoints=None,
awc_modes=None,
awc_amplitudes=None,
awc_frequencies=None,
disable_turbines=None,
):
"""Pass to the FlorisModel set function. To allow users
to directly replace a FlorisModel object with this
UncertainFlorisModel object, this function is required."""
UncertainFlorisModel object, this function is required
Args:
wind_speeds (NDArrayFloat | list[float] | None, optional): Wind speeds at each findex.
Defaults to None.
wind_directions (NDArrayFloat | list[float] | None, optional): Wind directions at each
findex. Defaults to None.
wind_shear (float | None, optional): Wind shear exponent. Defaults to None.
wind_veer (float | None, optional): Wind veer. Defaults to None.
reference_wind_height (float | None, optional): Reference wind height. Defaults to None.
turbulence_intensities (NDArrayFloat | list[float] | None, optional): Turbulence
intensities at each findex. Defaults to None.
air_density (float | None, optional): Air density. Defaults to None.
layout_x (NDArrayFloat | list[float] | None, optional): X-coordinates of the turbines.
Defaults to None.
layout_y (NDArrayFloat | list[float] | None, optional): Y-coordinates of the turbines.
Defaults to None.
turbine_type (list | None, optional): Turbine type. Defaults to None.
turbine_library_path (str | Path | None, optional): Path to the turbine library.
Defaults to None.
solver_settings (dict | None, optional): Solver settings. Defaults to None.
heterogeneous_inflow_config (None, optional): heterogeneous inflow configuration.
Defaults to None.
wind_data (type[WindDataBase] | None, optional): Wind data. Defaults to None.
yaw_angles (NDArrayFloat | list[float] | None, optional): Turbine yaw angles.
Defaults to None.
power_setpoints (NDArrayFloat | list[float] | list[float, None] | None, optional):
Turbine power setpoints.
disable_turbines (NDArrayBool | list[bool] | None, optional): NDArray with dimensions
n_findex x n_turbines. True values indicate the turbine is disabled at that findex
and the power setpoint at that position is set to 0. Defaults to None.
"""

if layout is not None:
msg = "Use the `layout_x` and `layout_y` parameters in place of `layout` "
Expand All @@ -183,7 +230,16 @@ def set(
layout_x=layout_x,
layout_y=layout_y,
turbine_type=turbine_type,
turbine_library_path=turbine_library_path,
solver_settings=solver_settings,
heterogeneous_inflow_config=heterogeneous_inflow_config,
wind_data=wind_data,
yaw_angles=yaw_angles,
power_setpoints=power_setpoints,
awc_modes=awc_modes,
awc_amplitudes=awc_amplitudes,
awc_frequencies=awc_frequencies,
disable_turbines=disable_turbines,
)

# Reinitialize settings
Expand Down Expand Up @@ -269,20 +325,26 @@ def run(self):
"'get_turbine_powers' or 'get_farm_power' directly."
)

def get_turbine_powers(self, yaw_angles=None):
def get_turbine_powers(self, yaw_angles=None, no_wake=False):
# Retrieve multiargs: preprocessing
t0 = timerpc()
multiargs = self._preprocessing(yaw_angles)
t_preparation = timerpc() - t0

# Set the function based on whether wake is disabled
if not no_wake:
turbine_power_function = _get_turbine_powers_serial
else:
turbine_power_function = _get_turbine_powers_serial_no_wake

# Perform parallel calculation
t1 = timerpc()
with self._PoolExecutor(self.max_workers) as p:
if (self.interface == "mpi4py") or (self.interface == "multiprocessing"):
out = p.starmap(_get_turbine_powers_serial, multiargs)
out = p.starmap(turbine_power_function, multiargs)
else:
out = p.map(
_get_turbine_powers_serial,
turbine_power_function,
[j[0] for j in multiargs],
[j[1] for j in multiargs]
)
Expand Down Expand Up @@ -316,7 +378,7 @@ def get_turbine_powers(self, yaw_angles=None):

return turbine_powers

def get_farm_power(self, yaw_angles=None, turbine_weights=None):
def get_farm_power(self, yaw_angles=None, turbine_weights=None, no_wake=False):
if turbine_weights is None:
# Default to equal weighing of all turbines when turbine_weights is None
turbine_weights = np.ones(
Expand All @@ -338,7 +400,7 @@ def get_farm_power(self, yaw_angles=None, turbine_weights=None):
)

# Calculate all turbine powers and apply weights
turbine_powers = self.get_turbine_powers(yaw_angles=yaw_angles)
turbine_powers = self.get_turbine_powers(yaw_angles=yaw_angles, no_wake=no_wake)
turbine_powers = np.multiply(turbine_weights, turbine_powers)

return np.sum(turbine_powers, axis=1)
Expand Down Expand Up @@ -392,16 +454,17 @@ def get_farm_AEP(
watt-hours.
"""

# If no_wake==True, ignore parallelization because it's fast enough
if no_wake:
return self.fmodel.get_farm_AEP(
freq=freq,
cut_in_wind_speed=cut_in_wind_speed,
cut_out_wind_speed=cut_out_wind_speed,
yaw_angles=yaw_angles,
turbine_weights=turbine_weights,
no_wake=no_wake
)
# This code is out of date, let's just thread it through
# # If no_wake==True, ignore parallelization because it's fast enough
# if no_wake:
# return self.fmodel.get_farm_AEP(
# freq=freq,
# cut_in_wind_speed=cut_in_wind_speed,
# cut_out_wind_speed=cut_out_wind_speed,
# yaw_angles=yaw_angles,
# turbine_weights=turbine_weights,
# no_wake=no_wake
# )

# Verify dimensions of the variable "freq"
if ((self._is_uncertain and np.shape(freq)[0] != self._n_unexpanded) or
Expand Down Expand Up @@ -438,7 +501,9 @@ def get_farm_AEP(
)

farm_power = (
self.get_farm_power(yaw_angles=yaw_angles, turbine_weights=turbine_weights)
self.get_farm_power(yaw_angles=yaw_angles,
turbine_weights=turbine_weights,
no_wake=no_wake)
)

# Finally, calculate AEP in GWh
Expand Down Expand Up @@ -523,6 +588,67 @@ def optimize_yaw_angles(

return df_opt

def get_operation_model(self):
"""Get the operation model of underlying fmodel.
Returns:
str: The operation_model.
"""
return self.fmodel.get_operation_model()

def set_operation_model(self, operation_model):
"""Set the operation model of underlying fmodel.
Args:
operation_model (str): The operation model to set.
"""
self.fmodel.set_operation_model(operation_model)

# Reinitialize settings
self.__init__(
fmodel=self.fmodel,
max_workers=self._max_workers,
n_wind_condition_splits=self._n_wind_condition_splits,
interface=self.interface,
propagate_flowfield_from_workers=self.propagate_flowfield_from_workers,
print_timings=self.print_timings,
)

def get_param(self, param, param_idx=None):
"""Get the parameter of underlying fmodel.
Args:
param (List[str]): A list of keys to traverse the FlorisModel dictionary.
param_idx (Optional[int], optional): The index to get the value at. Defaults to None.
If None, the entire parameter is returned.
Returns:
Any: The value of the parameter.
"""
return self.fmodel.get_param(param, param_idx)

def set_param(self, param, value, param_idx=None):
"""Set the parameter of underlying fmodel.
Args:
param (List[str]): A list of keys to traverse the FlorisModel dictionary.
value (Any): The value to set.
param_idx (Optional[int], optional): The index to set the value at. Defaults to None.
"""
self.fmodel.set_param(param, value, param_idx)

# Reinitialize settings
self.__init__(
fmodel=self.fmodel,
max_workers=self._max_workers,
n_wind_condition_splits=self._n_wind_condition_splits,
interface=self.interface,
propagate_flowfield_from_workers=self.propagate_flowfield_from_workers,
print_timings=self.print_timings,
)



@property
def layout_x(self):
return self.fmodel.layout_x
Expand Down Expand Up @@ -550,8 +676,3 @@ def n_findex(self):
@property
def n_turbines(self):
return self.fmodel.n_turbines


# @property
# def floris(self):
# return self.fmodel.core
33 changes: 32 additions & 1 deletion tests/parallel_floris_model_integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
def test_parallel_turbine_powers(sample_inputs_fixture):
"""
The parallel computing interface behaves like the floris interface, but distributes
calculations among available cores to speep up the necessary computations. This test compares
calculations among available cores to speed up the necessary computations. This test compares
the individual turbine powers computed with the parallel interface to those computed with
the serial floris interface. The expected result is that the turbine powers should be
exactly the same.
Expand Down Expand Up @@ -138,3 +138,34 @@ def test_parallel_uncertain_get_AEP(sample_inputs_fixture):
parallel_farm_AEP = pfmodel.get_farm_AEP(freq=freq)

assert np.allclose(parallel_farm_AEP, serial_farm_AEP)


def test_parallel_uncertain_get_AEP_no_wake(sample_inputs_fixture):
"""
"""
sample_inputs_fixture.core["wake"]["model_strings"]["velocity_model"] = VELOCITY_MODEL
sample_inputs_fixture.core["wake"]["model_strings"]["deflection_model"] = DEFLECTION_MODEL

freq=np.linspace(0, 1, 16)/8

ufmodel = UncertainFlorisModel(
sample_inputs_fixture.core,
wd_sample_points=[-3, 0, 3],
wd_std=3
)
pfmodel_input = copy.deepcopy(ufmodel)
ufmodel.run_no_wake()
serial_farm_AEP = ufmodel.get_farm_AEP(freq=freq)

pfmodel = ParallelFlorisModel(
fmodel=pfmodel_input,
max_workers=2,
n_wind_condition_splits=2,
interface="multiprocessing",
print_timings=False,
)

parallel_farm_AEP = pfmodel.get_farm_AEP(freq=freq, no_wake=True)

assert np.allclose(parallel_farm_AEP, serial_farm_AEP)

0 comments on commit 1464208

Please sign in to comment.