From 9da4cbb39d06a7c7d2fb6af1c8f2c5b5192bacc2 Mon Sep 17 00:00:00 2001 From: Rafael M Mudafort Date: Fri, 5 Apr 2024 15:54:57 -0500 Subject: [PATCH] Add helix model operation mode (#842) --- .../004_helix_active_wake_mixing.py | 160 ++++++++++++++++++ ...03_tilt_driven_vertical_wake_deflection.py | 0 examples/inputs/cc.yaml | 1 + examples/inputs/emgauss.yaml | 1 + examples/inputs/emgauss_helix.yaml | 109 ++++++++++++ examples/inputs/gch.yaml | 4 + examples/inputs/gch_heterogeneous_inflow.yaml | 1 + examples/inputs/gch_multi_dim_cp_ct.yaml | 1 + .../inputs/gch_multiple_turbine_types.yaml | 1 + examples/inputs/jensen.yaml | 1 + examples/inputs/turbopark.yaml | 1 + examples/inputs_floating/emgauss_fixed.yaml | 1 + .../inputs_floating/emgauss_floating.yaml | 1 + .../emgauss_floating_fixedtilt15.yaml | 1 + .../emgauss_floating_fixedtilt5.yaml | 1 + examples/inputs_floating/gch_fixed.yaml | 1 + examples/inputs_floating/gch_floating.yaml | 1 + .../gch_floating_defined_floating.yaml | 1 + floris/core/core.py | 14 ++ floris/core/farm.py | 51 ++++++ floris/core/solver.py | 49 ++++++ floris/core/turbine/__init__.py | 1 + floris/core/turbine/operation_models.py | 106 ++++++++++++ floris/core/turbine/turbine.py | 33 ++++ floris/core/wake.py | 1 + floris/core/wake_velocity/empirical_gauss.py | 22 +++ floris/floris_model.py | 71 +++++++- floris/turbine_library/iea_10MW.yaml | 5 + floris/turbine_library/iea_15MW.yaml | 5 + floris/turbine_library/nrel_5MW.yaml | 6 + floris/type_dec.py | 1 + tests/conftest.py | 10 +- tests/data/input_full.yaml | 1 + tests/farm_unit_test.py | 6 + .../cumulative_curl_regression_test.py | 36 ++++ .../empirical_gauss_regression_test.py | 155 +++++++++++++++++ tests/reg_tests/gauss_regression_test.py | 52 ++++++ .../jensen_jimenez_regression_test.py | 20 +++ tests/reg_tests/none_regression_test.py | 12 ++ tests/reg_tests/turbopark_regression_test.py | 20 +++ tests/turbine_multi_dim_unit_test.py | 14 +- ...rbine_operation_models_integration_test.py | 83 +++++++++ tests/turbine_unit_test.py | 22 +++ 43 files changed, 1080 insertions(+), 3 deletions(-) create mode 100644 examples/examples_control_types/004_helix_active_wake_mixing.py rename examples/{examples_emgauss => examples_floating}/003_tilt_driven_vertical_wake_deflection.py (100%) create mode 100644 examples/inputs/emgauss_helix.yaml diff --git a/examples/examples_control_types/004_helix_active_wake_mixing.py b/examples/examples_control_types/004_helix_active_wake_mixing.py new file mode 100644 index 000000000..456766ba6 --- /dev/null +++ b/examples/examples_control_types/004_helix_active_wake_mixing.py @@ -0,0 +1,160 @@ +# Copyright 2024 NREL + +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# See https://floris.readthedocs.io for documentation + +import matplotlib.pyplot as plt +import numpy as np +import yaml + +import floris.flow_visualization as flowviz +from floris import FlorisModel + + +""" +Example to test out using helix wake mixing of upstream turbines. +Helix wake mixing is turned on at turbine 1, off at turbines 2 to 4; +Turbine 2 is in wake turbine 1, turbine 4 in wake of turbine 3. +""" + +# Grab model of FLORIS and update to awc-enabled turbines +fmodel = FlorisModel("../inputs/emgauss_helix.yaml") +fmodel.set_operation_model("awc") + +# Set the wind directions and speeds to be constant over N different helix amplitudes +N = 1 +awc_modes = np.array(["helix", "baseline", "baseline", "baseline"]).reshape(4, N).T +awc_amplitudes = np.array([2.5, 0, 0, 0]).reshape(4, N).T + +# Create 4 WT WF layout with lateral offset of 3D and streamwise offset of 4D +D = 240 +fmodel.set( + layout_x=[0.0, 4*D, 0.0, 4*D], + layout_y=[0.0, 0.0, -3*D, -3*D], + wind_directions=270 * np.ones(N), + wind_speeds=8.0 * np.ones(N), + turbulence_intensities=0.06*np.ones(N), + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes +) +fmodel.run() +turbine_powers = fmodel.get_turbine_powers() + +# Plot the flow fields for T1 awc_amplitude = 2.5 +horizontal_plane = fmodel.calculate_horizontal_plane( + x_resolution=200, + y_resolution=100, + height=150.0, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes +) + +y_plane_baseline = fmodel.calculate_y_plane( + x_resolution=200, + z_resolution=100, + crossstream_dist=0.0, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes +) +y_plane_helix = fmodel.calculate_y_plane( + x_resolution=200, + z_resolution=100, + crossstream_dist=-3*D, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes +) + +cross_plane = fmodel.calculate_cross_plane( + y_resolution=100, + z_resolution=100, + downstream_dist=720.0, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes +) + +# Create the plots +fig, ax_list = plt.subplots(2, 2, figsize=(10, 8), tight_layout=True) +ax_list = ax_list.flatten() +flowviz.visualize_cut_plane( + horizontal_plane, + ax=ax_list[0], + label_contours=True, + title="Horizontal" +) +flowviz.visualize_cut_plane( + cross_plane, + ax=ax_list[2], + label_contours=True, + title="Spanwise profile at 3D" +) + +# fig2, ax_list2 = plt.subplots(2, 1, figsize=(10, 8), tight_layout=True) +# ax_list2 = ax_list2.flatten() +flowviz.visualize_cut_plane( + y_plane_baseline, + ax=ax_list[1], + label_contours=True, + title="Streamwise profile, helix" +) +flowviz.visualize_cut_plane( + y_plane_helix, + ax=ax_list[3], + label_contours=True, + title="Streamwise profile, baseline" +) + +# Calculate the effect of changing awc_amplitudes +N = 50 +awc_amplitudes = np.array([ + np.linspace(0, 5, N), + np.zeros(N), np.zeros(N), np.zeros(N) + ]).reshape(4, N).T + +# Reset FlorisModel for different helix amplitudes +fmodel.set( + wind_directions=270 * np.ones(N), + wind_speeds=8 * np.ones(N), + turbulence_intensities=0.06*np.ones(N), + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes + ) +fmodel.run() +turbine_powers = fmodel.get_turbine_powers() + +# Plot the power as a function of helix amplitude +fig_power, ax_power = plt.subplots() +ax_power.fill_between( + awc_amplitudes[:, 0], + 0, + turbine_powers[:, 0]/1000, + color='C0', + label='Turbine 1' + ) +ax_power.fill_between( + awc_amplitudes[:, 0], + turbine_powers[:, 0]/1000, + turbine_powers[:, :2].sum(axis=1)/1000, + color='C1', + label='Turbine 2' + ) +ax_power.plot( + awc_amplitudes[:, 0], + turbine_powers[:,:2].sum(axis=1)/1000, + color='k', + label='Farm' + ) + +ax_power.set_xlabel("Upstream turbine helix amplitude [deg]") +ax_power.set_ylabel("Power [kW]") +ax_power.legend() + +flowviz.show() diff --git a/examples/examples_emgauss/003_tilt_driven_vertical_wake_deflection.py b/examples/examples_floating/003_tilt_driven_vertical_wake_deflection.py similarity index 100% rename from examples/examples_emgauss/003_tilt_driven_vertical_wake_deflection.py rename to examples/examples_floating/003_tilt_driven_vertical_wake_deflection.py diff --git a/examples/inputs/cc.yaml b/examples/inputs/cc.yaml index de626ff8f..1935c004f 100644 --- a/examples/inputs/cc.yaml +++ b/examples/inputs/cc.yaml @@ -49,6 +49,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true enable_transverse_velocities: true + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs/emgauss.yaml b/examples/inputs/emgauss.yaml index 89caef95b..8f8340a1b 100644 --- a/examples/inputs/emgauss.yaml +++ b/examples/inputs/emgauss.yaml @@ -48,6 +48,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: true + enable_active_wake_mixing: false enable_transverse_velocities: false wake_deflection_parameters: diff --git a/examples/inputs/emgauss_helix.yaml b/examples/inputs/emgauss_helix.yaml new file mode 100644 index 000000000..48a6add0d --- /dev/null +++ b/examples/inputs/emgauss_helix.yaml @@ -0,0 +1,109 @@ + +name: Emperical Gaussian +description: Three turbines using empirical Gaussian model +floris_version: v4.0 + +logging: + console: + enable: true + level: WARNING + file: + enable: false + level: WARNING + +solver: + type: turbine_grid + turbine_grid_points: 3 + +farm: + layout_x: + - 0.0 + - 630.0 + - 1260.0 + layout_y: + - 0.0 + - 0.0 + - 0.0 + turbine_type: + - iea_15MW + +flow_field: + air_density: 1.225 + reference_wind_height: -1 # -1 is code for use the hub height + turbulence_intensities: + - 0.06 + wind_directions: + - 270.0 + wind_shear: 0.12 + wind_speeds: + - 8.0 + wind_veer: 0.0 + +wake: + model_strings: + combination_model: sosfs + deflection_model: empirical_gauss + turbulence_model: wake_induced_mixing + velocity_model: empirical_gauss + + enable_secondary_steering: false + enable_yaw_added_recovery: false + enable_active_wake_mixing: true + enable_transverse_velocities: false + + wake_deflection_parameters: + gauss: + ad: 0.0 + alpha: 0.58 + bd: 0.0 + beta: 0.077 + dm: 1.0 + ka: 0.38 + kb: 0.004 + jimenez: + ad: 0.0 + bd: 0.0 + kd: 0.05 + empirical_gauss: + horizontal_deflection_gain_D: 3.0 + vertical_deflection_gain_D: -1 + deflection_rate: 30 + mixing_gain_deflection: 0.0 + yaw_added_mixing_gain: 0.0 + + wake_velocity_parameters: + cc: + a_s: 0.179367259 + b_s: 0.0118889215 + c_s1: 0.0563691592 + c_s2: 0.13290157 + a_f: 3.11 + b_f: -0.68 + c_f: 2.41 + alpha_mod: 1.0 + gauss: + alpha: 0.58 + beta: 0.077 + ka: 0.38 + kb: 0.004 + jensen: + we: 0.05 + empirical_gauss: + wake_expansion_rates: + - 0.023 + - 0.008 + breakpoints_D: + - 10 + sigma_0_D: 0.28 + smoothing_length_D: 2.0 + mixing_gain_velocity: 2.0 + awc_wake_exp: 1.2 + awc_wake_denominator: 400 + wake_turbulence_parameters: + crespo_hernandez: + initial: 0.1 + constant: 0.5 + ai: 0.8 + downstream: -0.32 + wake_induced_mixing: + atmospheric_ti_gain: 0.0 diff --git a/examples/inputs/gch.yaml b/examples/inputs/gch.yaml index 79b0b8629..ced8eb38f 100644 --- a/examples/inputs/gch.yaml +++ b/examples/inputs/gch.yaml @@ -178,6 +178,10 @@ wake: # Can be "true" or "false". enable_yaw_added_recovery: true + ### + # Can be "true" or "false". + enable_active_wake_mixing: false + ### # Can be "true" or "false". enable_transverse_velocities: true diff --git a/examples/inputs/gch_heterogeneous_inflow.yaml b/examples/inputs/gch_heterogeneous_inflow.yaml index 121457f15..28f9bf6f5 100644 --- a/examples/inputs/gch_heterogeneous_inflow.yaml +++ b/examples/inputs/gch_heterogeneous_inflow.yaml @@ -62,6 +62,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true enable_transverse_velocities: true + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs/gch_multi_dim_cp_ct.yaml b/examples/inputs/gch_multi_dim_cp_ct.yaml index 236bb63f8..592b6172f 100644 --- a/examples/inputs/gch_multi_dim_cp_ct.yaml +++ b/examples/inputs/gch_multi_dim_cp_ct.yaml @@ -52,6 +52,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true enable_transverse_velocities: true + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs/gch_multiple_turbine_types.yaml b/examples/inputs/gch_multiple_turbine_types.yaml index 366f4e9c0..80682aa28 100644 --- a/examples/inputs/gch_multiple_turbine_types.yaml +++ b/examples/inputs/gch_multiple_turbine_types.yaml @@ -48,6 +48,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: false enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs/jensen.yaml b/examples/inputs/jensen.yaml index c0f95de6e..f3b81747d 100644 --- a/examples/inputs/jensen.yaml +++ b/examples/inputs/jensen.yaml @@ -49,6 +49,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: false enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs/turbopark.yaml b/examples/inputs/turbopark.yaml index 598ed87a0..c4ffbfa43 100644 --- a/examples/inputs/turbopark.yaml +++ b/examples/inputs/turbopark.yaml @@ -49,6 +49,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: false enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/emgauss_fixed.yaml b/examples/inputs_floating/emgauss_fixed.yaml index 026710481..2daf9e2a3 100644 --- a/examples/inputs_floating/emgauss_fixed.yaml +++ b/examples/inputs_floating/emgauss_fixed.yaml @@ -49,6 +49,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: true enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/emgauss_floating.yaml b/examples/inputs_floating/emgauss_floating.yaml index 253944aaf..28dc0a747 100644 --- a/examples/inputs_floating/emgauss_floating.yaml +++ b/examples/inputs_floating/emgauss_floating.yaml @@ -49,6 +49,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: true enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml b/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml index c34b38250..0160d9605 100644 --- a/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml +++ b/examples/inputs_floating/emgauss_floating_fixedtilt15.yaml @@ -45,6 +45,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: true enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml b/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml index 398c6eb29..7477d5132 100644 --- a/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml +++ b/examples/inputs_floating/emgauss_floating_fixedtilt5.yaml @@ -45,6 +45,7 @@ wake: enable_secondary_steering: false enable_yaw_added_recovery: true enable_transverse_velocities: false + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/gch_fixed.yaml b/examples/inputs_floating/gch_fixed.yaml index 3290d6fa1..d9f961701 100644 --- a/examples/inputs_floating/gch_fixed.yaml +++ b/examples/inputs_floating/gch_fixed.yaml @@ -45,6 +45,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true enable_transverse_velocities: true + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/gch_floating.yaml b/examples/inputs_floating/gch_floating.yaml index c342473f6..4af183aca 100644 --- a/examples/inputs_floating/gch_floating.yaml +++ b/examples/inputs_floating/gch_floating.yaml @@ -46,6 +46,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true enable_transverse_velocities: true + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/examples/inputs_floating/gch_floating_defined_floating.yaml b/examples/inputs_floating/gch_floating_defined_floating.yaml index 47288c718..ecb5b3b0a 100644 --- a/examples/inputs_floating/gch_floating_defined_floating.yaml +++ b/examples/inputs_floating/gch_floating_defined_floating.yaml @@ -45,6 +45,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true enable_transverse_velocities: true + enable_active_wake_mixing: false wake_deflection_parameters: gauss: diff --git a/floris/core/core.py b/floris/core/core.py index 084f0a717..89af93bcf 100644 --- a/floris/core/core.py +++ b/floris/core/core.py @@ -86,6 +86,9 @@ def __attrs_post_init__(self) -> None: self.farm.set_yaw_angles_to_ref_yaw(self.flow_field.n_findex) self.farm.set_tilt_to_ref_tilt(self.flow_field.n_findex) self.farm.set_power_setpoints_to_ref_power(self.flow_field.n_findex) + self.farm.set_awc_modes_to_ref_mode(self.flow_field.n_findex) + self.farm.set_awc_amplitudes_to_ref_amp(self.flow_field.n_findex) + self.farm.set_awc_frequencies_to_ref_freq(self.flow_field.n_findex) if self.solver["type"] == "turbine_grid": self.grid = TurbineGrid( @@ -159,6 +162,17 @@ def steady_state_atmospheric_condition(self): "vertical wake deflection will occur." ) + operation_model_awc = False + for td in self.farm.turbine_definitions: + if "operation_model" in td and td["operation_model"] == "awc": + operation_model_awc = True + if vel_model != "empirical_gauss" and operation_model_awc: + self.logger.warning( + f"The current model `{vel_model}` does not account for additional wake mixing " + + "due to active wake control. Corrections to power and thrust coefficient can " + + "be included, but no enhanced wake recovery will occur." + ) + if vel_model=="cc": cc_solver( self.farm, diff --git a/floris/core/farm.py b/floris/core/farm.py index 93bd246b6..d1d2ea0ed 100644 --- a/floris/core/farm.py +++ b/floris/core/farm.py @@ -28,6 +28,7 @@ iter_validator, NDArrayFloat, NDArrayObject, + NDArrayStr, ) from floris.utilities import load_yaml @@ -85,6 +86,15 @@ class Farm(BaseClass): power_setpoints: NDArrayFloat = field(init=False) power_setpoints_sorted: NDArrayFloat = field(init=False) + awc_modes: NDArrayStr = field(init=False) + awc_modes_sorted: NDArrayStr = field(init=False) + + awc_amplitudes: NDArrayFloat = field(init=False) + awc_amplitudes_sorted: NDArrayFloat = field(init=False) + + awc_frequencies: NDArrayFloat = field(init=False) + awc_frequencies_sorted: NDArrayFloat = field(init=False) + hub_heights: NDArrayFloat = field(init=False) hub_heights_sorted: NDArrayFloat = field(init=False, factory=list) @@ -236,6 +246,21 @@ def initialize(self, sorted_indices): sorted_indices[:, :, 0, 0], axis=1, ) + self.awc_modes_sorted = np.take_along_axis( + self.awc_modes, + sorted_indices[:, :, 0, 0], + axis=1, + ) + self.awc_amplitudes_sorted = np.take_along_axis( + self.awc_amplitudes, + sorted_indices[:, :, 0, 0], + axis=1, + ) + self.awc_frequencies_sorted = np.take_along_axis( + self.awc_frequencies, + sorted_indices[:, :, 0, 0], + axis=1, + ) self.state = State.INITIALIZED def construct_hub_heights(self): @@ -356,6 +381,32 @@ def set_power_setpoints_to_ref_power(self, n_findex: int): self.set_power_setpoints(power_setpoints) self.power_setpoints_sorted = POWER_SETPOINT_DEFAULT * np.ones((n_findex, self.n_turbines)) + def set_awc_modes(self, awc_modes: NDArrayStr): + self.awc_modes = np.array(awc_modes) + + def set_awc_modes_to_ref_mode(self, n_findex: int): + # awc_modes = np.empty((n_findex, self.n_turbines))\ + awc_modes = np.array([["baseline"]*self.n_turbines]*n_findex) + self.set_awc_modes(awc_modes) + # self.awc_modes_sorted = np.empty((n_findex, self.n_turbines)) + self.awc_modes_sorted = np.array([["baseline"]*self.n_turbines]*n_findex) + + def set_awc_amplitudes(self, awc_amplitudes: NDArrayFloat): + self.awc_amplitudes = np.array(awc_amplitudes) + + def set_awc_amplitudes_to_ref_amp(self, n_findex: int): + awc_amplitudes = np.zeros((n_findex, self.n_turbines)) + self.set_awc_amplitudes(awc_amplitudes) + self.awc_amplitudes_sorted = np.zeros((n_findex, self.n_turbines)) + + def set_awc_frequencies(self, awc_frequencies: NDArrayFloat): + self.awc_frequencies = np.array(awc_frequencies) + + def set_awc_frequencies_to_ref_freq(self, n_findex: int): + awc_frequencies = np.zeros((n_findex, self.n_turbines)) + self.set_awc_frequencies(awc_frequencies) + self.awc_frequencies_sorted = np.zeros((n_findex, self.n_turbines)) + def calculate_tilt_for_eff_velocities(self, rotor_effective_velocities): tilt_angles = compute_tilt_angles_for_floating_turbines_map( self.turbine_type_map_sorted, diff --git a/floris/core/solver.py b/floris/core/solver.py index a21978156..8307b27c8 100644 --- a/floris/core/solver.py +++ b/floris/core/solver.py @@ -23,6 +23,7 @@ wake_added_yaw, yaw_added_turbulence_mixing, ) +from floris.core.wake_velocity.empirical_gauss import awc_added_wake_mixing from floris.type_dec import NDArrayFloat from floris.utilities import cosd @@ -94,6 +95,8 @@ def sequential_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes_sorted, + awc_amplitudes=farm.awc_amplitudes_sorted, thrust_coefficient_functions=farm.turbine_thrust_coefficient_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -113,6 +116,8 @@ def sequential_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes_sorted, + awc_amplitudes=farm.awc_amplitudes_sorted, axial_induction_functions=farm.turbine_axial_induction_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -326,6 +331,8 @@ def full_flow_sequential_solver( yaw_angles=turbine_grid_farm.yaw_angles_sorted, tilt_angles=turbine_grid_farm.tilt_angles_sorted, power_setpoints=turbine_grid_farm.power_setpoints_sorted, + awc_modes=turbine_grid_farm.awc_modes_sorted, + awc_amplitudes=turbine_grid_farm.awc_amplitudes_sorted, thrust_coefficient_functions=turbine_grid_farm.turbine_thrust_coefficient_functions, tilt_interps=turbine_grid_farm.turbine_tilt_interps, correct_cp_ct_for_tilt=turbine_grid_farm.correct_cp_ct_for_tilt_sorted, @@ -345,6 +352,8 @@ def full_flow_sequential_solver( yaw_angles=turbine_grid_farm.yaw_angles_sorted, tilt_angles=turbine_grid_farm.tilt_angles_sorted, power_setpoints=turbine_grid_farm.power_setpoints_sorted, + awc_modes=turbine_grid_farm.awc_modes_sorted, + awc_amplitudes=turbine_grid_farm.awc_amplitudes_sorted, axial_induction_functions=turbine_grid_farm.turbine_axial_induction_functions, tilt_interps=turbine_grid_farm.turbine_tilt_interps, correct_cp_ct_for_tilt=turbine_grid_farm.correct_cp_ct_for_tilt_sorted, @@ -501,6 +510,8 @@ def cc_solver( farm.yaw_angles_sorted, farm.tilt_angles_sorted, farm.power_setpoints_sorted, + farm.awc_modes_sorted, + farm.awc_amplitudes_sorted, farm.turbine_thrust_coefficient_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -517,6 +528,8 @@ def cc_solver( farm.yaw_angles_sorted, farm.tilt_angles_sorted, farm.power_setpoints_sorted, + farm.awc_modes_sorted, + farm.awc_amplitudes_sorted, farm.turbine_axial_induction_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -538,6 +551,8 @@ def cc_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes_sorted, + awc_amplitudes=farm.awc_amplitudes_sorted, axial_induction_functions=farm.turbine_axial_induction_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -751,6 +766,8 @@ def full_flow_cc_solver( yaw_angles=turbine_grid_farm.yaw_angles_sorted, tilt_angles=turbine_grid_farm.tilt_angles_sorted, power_setpoints=turbine_grid_farm.power_setpoints_sorted, + awc_modes=turbine_grid_farm.awc_modes, + awc_amplitudes=turbine_grid_farm.awc_amplitudes_sorted, thrust_coefficient_functions=turbine_grid_farm.turbine_thrust_coefficient_functions, tilt_interps=turbine_grid_farm.turbine_tilt_interps, correct_cp_ct_for_tilt=turbine_grid_farm.correct_cp_ct_for_tilt_sorted, @@ -768,6 +785,8 @@ def full_flow_cc_solver( yaw_angles=turbine_grid_farm.yaw_angles_sorted, tilt_angles=turbine_grid_farm.tilt_angles_sorted, power_setpoints=turbine_grid_farm.power_setpoints_sorted, + awc_modes=turbine_grid_farm.awc_modes, + awc_amplitudes=turbine_grid_farm.awc_amplitudes_sorted, axial_induction_functions=turbine_grid_farm.turbine_axial_induction_functions, tilt_interps=turbine_grid_farm.turbine_tilt_interps, correct_cp_ct_for_tilt=turbine_grid_farm.correct_cp_ct_for_tilt_sorted, @@ -908,6 +927,8 @@ def turbopark_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes, + awc_amplitudes=farm.awc_amplitudes_sorted, thrust_coefficient_functions=farm.turbine_thrust_coefficient_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -924,6 +945,8 @@ def turbopark_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes, + awc_amplitudes=farm.awc_amplitudes_sorted, thrust_coefficient_functions=farm.turbine_thrust_coefficient_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -943,6 +966,8 @@ def turbopark_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes, + awc_amplitudes=farm.awc_amplitudes_sorted, axial_induction_functions=farm.turbine_axial_induction_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -989,6 +1014,8 @@ def turbopark_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes, + awc_amplitudes=farm.awc_amplitudes_sorted, thrust_coefficient_functions=farm.turbine_thrust_coefficient_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -1164,6 +1191,8 @@ def empirical_gauss_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes_sorted, + awc_amplitudes=farm.awc_amplitudes_sorted, thrust_coefficient_functions=farm.turbine_thrust_coefficient_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -1183,6 +1212,8 @@ def empirical_gauss_solver( yaw_angles=farm.yaw_angles_sorted, tilt_angles=farm.tilt_angles_sorted, power_setpoints=farm.power_setpoints_sorted, + awc_modes=farm.awc_modes_sorted, + awc_amplitudes=farm.awc_amplitudes_sorted, axial_induction_functions=farm.turbine_axial_induction_functions, tilt_interps=farm.turbine_tilt_interps, correct_cp_ct_for_tilt=farm.correct_cp_ct_for_tilt_sorted, @@ -1197,6 +1228,9 @@ def empirical_gauss_solver( # get the first index here (0:1) axial_induction_i = axial_induction_i[:, 0:1, None, None] yaw_angle_i = farm.yaw_angles_sorted[:, i:i+1, None, None] + awc_mode_i = farm.awc_modes_sorted[:, i:i+1, None, None] + awc_amplitude_i = farm.awc_amplitudes_sorted[:, i:i+1, None, None] + awc_frequency_i = farm.awc_frequencies_sorted[:, i:i+1, None, None] hub_height_i = farm.hub_heights_sorted[:, i:i+1, None, None] rotor_diameter_i = farm.rotor_diameters_sorted[:, i:i+1, None, None] @@ -1230,6 +1264,17 @@ def empirical_gauss_solver( model_manager.deflection_model.yaw_added_mixing_gain ) + if model_manager.enable_active_wake_mixing: + # Influence of awc on turbine's own wake + mixing_factor[:, i:i+1, i] += \ + awc_added_wake_mixing( + awc_mode_i, + awc_amplitude_i, + awc_frequency_i, + model_manager.velocity_model.awc_wake_exp, + model_manager.velocity_model.awc_wake_denominator + ) + # Extract total wake induced mixing for turbine i mixing_i = np.linalg.norm( mixing_factor[:, i:i+1, :, None], @@ -1367,6 +1412,8 @@ def full_flow_empirical_gauss_solver( yaw_angles=turbine_grid_farm.yaw_angles_sorted, tilt_angles=turbine_grid_farm.tilt_angles_sorted, power_setpoints=turbine_grid_farm.power_setpoints_sorted, + awc_modes=turbine_grid_farm.awc_modes_sorted, + awc_amplitudes=turbine_grid_farm.awc_amplitudes_sorted, thrust_coefficient_functions=turbine_grid_farm.turbine_thrust_coefficient_functions, tilt_interps=turbine_grid_farm.turbine_tilt_interps, correct_cp_ct_for_tilt=turbine_grid_farm.correct_cp_ct_for_tilt_sorted, @@ -1386,6 +1433,8 @@ def full_flow_empirical_gauss_solver( yaw_angles=turbine_grid_farm.yaw_angles_sorted, tilt_angles=turbine_grid_farm.tilt_angles_sorted, power_setpoints=turbine_grid_farm.power_setpoints_sorted, + awc_modes=turbine_grid_farm.awc_modes_sorted, + awc_amplitudes=turbine_grid_farm.awc_amplitudes_sorted, axial_induction_functions=turbine_grid_farm.turbine_axial_induction_functions, tilt_interps=turbine_grid_farm.turbine_tilt_interps, correct_cp_ct_for_tilt=turbine_grid_farm.correct_cp_ct_for_tilt_sorted, diff --git a/floris/core/turbine/__init__.py b/floris/core/turbine/__init__.py index 5f361f463..6216fe2b0 100644 --- a/floris/core/turbine/__init__.py +++ b/floris/core/turbine/__init__.py @@ -1,5 +1,6 @@ from floris.core.turbine.operation_models import ( + AWCTurbine, CosineLossTurbine, MixedOperationTurbine, SimpleDeratingTurbine, diff --git a/floris/core/turbine/operation_models.py b/floris/core/turbine/operation_models.py index a4ddfddfe..8fcbdb540 100644 --- a/floris/core/turbine/operation_models.py +++ b/floris/core/turbine/operation_models.py @@ -484,3 +484,109 @@ def axial_induction( )[neither_mask] return axial_inductions + +@define +class AWCTurbine(BaseOperationModel): + """ + power_thrust_table is a dictionary (normally defined on the turbine input yaml) + that contains the parameters necessary to evaluate power(), thrust(), and axial_induction(). + + Feel free to put any Helix tuning parameters into here (they can be added to the turbine yaml). + Also, feel free to add any commanded inputs to power(), thrust_coefficient(), or + axial_induction(). For this operation model to receive those arguments, they'll need to be + added to the kwargs dictionaries in the respective functions on turbine.py. They won't affect + the other operation models. + """ + + def power( + power_thrust_table: dict, + velocities: NDArrayFloat, + air_density: float, + awc_modes: str, + awc_amplitudes: NDArrayFloat | None, + average_method: str = "cubic-mean", + cubature_weights: NDArrayFloat | None = None, + **_ # <- Allows other models to accept other keyword arguments + ): + base_powers = SimpleTurbine.power( + power_thrust_table=power_thrust_table, + velocities=velocities, + air_density=air_density, + average_method=average_method, + cubature_weights=cubature_weights + ) + + if (awc_modes == 'helix').any(): + if np.any(np.isclose( + base_powers/1000, + np.max(power_thrust_table['power']) + )): + raise UserWarning( + 'The selected wind speed is above or near rated wind speed. ' + '`AWCTurbine` operation model is not designed ' + 'or verified for above-rated conditions.' + ) + return base_powers * (1 - ( + power_thrust_table['helix_power_b'] + + power_thrust_table['helix_power_c']*base_powers + ) + *awc_amplitudes**power_thrust_table['helix_a'] + ) ## TODO: Should probably add max function here + if (awc_modes == 'baseline').any(): + return base_powers + else: + raise UserWarning( + 'Active wake mixing strategies other than the `helix` strategy ' + 'have not yet been implemented in FLORIS. Returning baseline power.' + ) + + + def thrust_coefficient( + power_thrust_table: dict, + velocities: NDArrayFloat, + awc_modes: str, + awc_amplitudes: NDArrayFloat | None, + average_method: str = "cubic-mean", + cubature_weights: NDArrayFloat | None = None, + **_ # <- Allows other models to accept other keyword arguments + ): + base_thrust_coefficients = SimpleTurbine.thrust_coefficient( + power_thrust_table=power_thrust_table, + velocities=velocities, + average_method=average_method, + cubature_weights=cubature_weights + ) + if (awc_modes == 'helix').any(): + return base_thrust_coefficients * (1 - ( + power_thrust_table['helix_thrust_b'] + + power_thrust_table['helix_thrust_c']*base_thrust_coefficients + ) + *awc_amplitudes**power_thrust_table['helix_a'] + ) + if (awc_modes == 'baseline').any(): + return base_thrust_coefficients + else: + raise UserWarning( + 'Active wake mixing strategies other than the `helix` strategy ' + 'have not yet been implemented in FLORIS. Returning baseline power.' + ) + + def axial_induction( + power_thrust_table: dict, + velocities: NDArrayFloat, + awc_modes: str, + awc_amplitudes: NDArrayFloat, + average_method: str = "cubic-mean", + cubature_weights: NDArrayFloat | None = None, + **_ # <- Allows other models to accept other keyword arguments + ): + thrust_coefficient = AWCTurbine.thrust_coefficient( + power_thrust_table=power_thrust_table, + velocities=velocities, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes, + average_method=average_method, + cubature_weights=cubature_weights, + ) + + return (1 - np.sqrt(1 - thrust_coefficient))/2 diff --git a/floris/core/turbine/turbine.py b/floris/core/turbine/turbine.py index 315eaabb9..e176e6a05 100644 --- a/floris/core/turbine/turbine.py +++ b/floris/core/turbine/turbine.py @@ -13,6 +13,7 @@ from floris.core import BaseClass from floris.core.turbine import ( + AWCTurbine, CosineLossTurbine, MixedOperationTurbine, SimpleDeratingTurbine, @@ -26,6 +27,7 @@ NDArrayFloat, NDArrayInt, NDArrayObject, + NDArrayStr, ) from floris.utilities import cosd @@ -36,6 +38,7 @@ "cosine-loss": CosineLossTurbine, "simple-derating": SimpleDeratingTurbine, "mixed": MixedOperationTurbine, + "awc": AWCTurbine, }, } @@ -75,6 +78,8 @@ def power( yaw_angles: NDArrayFloat, tilt_angles: NDArrayFloat, power_setpoints: NDArrayFloat, + awc_modes: NDArrayStr, + awc_amplitudes: NDArrayFloat, tilt_interps: dict[str, interp1d], turbine_type_map: NDArrayObject, turbine_power_thrust_tables: dict, @@ -97,6 +102,12 @@ def power( tilt_angles (NDArrayFloat[findex, turbines]): The tilt angle for each turbine. power_setpoints: (NDArrayFloat[findex, turbines]): Maximum power setpoint for each turbine [W]. + awc_modes: (NDArrayStr[findex, turbines]): awc excitation mode (currently, only "baseline" + and "helix" are implemented). + awc_modes: (NDArrayStr[findex, turbines]): awc excitation mode (currently, only "baseline" + and "helix" are implemented). + awc_amplitudes: (NDArrayFloat[findex, turbines]): awc excitation amplitude for each + turbine [deg]. tilt_interps (Iterable[tuple]): The tilt interpolation functions for each turbine. turbine_type_map: (NDArrayObject[wd, ws, turbines]): The Turbine type definition for @@ -131,6 +142,8 @@ def power( yaw_angles = yaw_angles[:, ix_filter] tilt_angles = tilt_angles[:, ix_filter] power_setpoints = power_setpoints[:, ix_filter] + awc_modes = awc_modes[:, ix_filter] + awc_amplitudes = awc_amplitudes[:, ix_filter] turbine_type_map = turbine_type_map[:, ix_filter] if type(correct_cp_ct_for_tilt) is bool: pass @@ -161,6 +174,8 @@ def power( "yaw_angles": yaw_angles, "tilt_angles": tilt_angles, "power_setpoints": power_setpoints, + "awc_modes": awc_modes, + "awc_amplitudes": awc_amplitudes, "tilt_interp": tilt_interps[turb_type], "average_method": average_method, "cubature_weights": cubature_weights, @@ -180,6 +195,8 @@ def thrust_coefficient( yaw_angles: NDArrayFloat, tilt_angles: NDArrayFloat, power_setpoints: NDArrayFloat, + awc_modes: NDArrayStr, + awc_amplitudes: NDArrayFloat, thrust_coefficient_functions: dict[str, Callable], tilt_interps: dict[str, interp1d], correct_cp_ct_for_tilt: NDArrayBool, @@ -203,6 +220,10 @@ def thrust_coefficient( tilt_angles (NDArrayFloat[findex, turbines]): The tilt angle for each turbine. power_setpoints: (NDArrayFloat[findex, turbines]): Maximum power setpoint for each turbine [W]. + awc_modes: (NDArrayStr[findex, turbines]): awc excitation mode (currently, only "baseline" + and "helix" are implemented). + awc_amplitudes: (NDArrayFloat[findex, turbines]): awc excitation amplitude for each + turbine [deg]. thrust_coefficient_functions (dict): The thrust coefficient functions for each turbine. Keys are the turbine type string and values are the callable functions. tilt_interps (Iterable[tuple]): The tilt interpolation functions for each @@ -232,6 +253,8 @@ def thrust_coefficient( yaw_angles = yaw_angles[:, ix_filter] tilt_angles = tilt_angles[:, ix_filter] power_setpoints = power_setpoints[:, ix_filter] + awc_modes = awc_modes[:, ix_filter] + awc_amplitudes = awc_amplitudes[:, ix_filter] turbine_type_map = turbine_type_map[:, ix_filter] if type(correct_cp_ct_for_tilt) is bool: pass @@ -262,6 +285,8 @@ def thrust_coefficient( "yaw_angles": yaw_angles, "tilt_angles": tilt_angles, "power_setpoints": power_setpoints, + "awc_modes": awc_modes, + "awc_amplitudes": awc_amplitudes, "tilt_interp": tilt_interps[turb_type], "average_method": average_method, "cubature_weights": cubature_weights, @@ -284,6 +309,8 @@ def axial_induction( yaw_angles: NDArrayFloat, tilt_angles: NDArrayFloat, power_setpoints: NDArrayFloat, + awc_modes: NDArrayStr, + awc_amplitudes: NDArrayFloat, axial_induction_functions: dict, tilt_interps: NDArrayObject, correct_cp_ct_for_tilt: NDArrayBool, @@ -304,6 +331,8 @@ def axial_induction( tilt_angles (NDArrayFloat[findex, turbines]): The tilt angle for each turbine. power_setpoints: (NDArrayFloat[findex, turbines]): Maximum power setpoint for each turbine [W]. + awc_amplitudes: (NDArrayFloat[findex, turbines]): awc excitation amplitude for each + turbine [deg]. axial_induction_functions (dict): The axial induction functions for each turbine. Keys are the turbine type string and values are the callable functions. tilt_interps (Iterable[tuple]): The tilt interpolation functions for each @@ -333,6 +362,8 @@ def axial_induction( yaw_angles = yaw_angles[:, ix_filter] tilt_angles = tilt_angles[:, ix_filter] power_setpoints = power_setpoints[:, ix_filter] + awc_modes = awc_modes[:, ix_filter] + awc_amplitudes = awc_amplitudes[:, ix_filter] turbine_type_map = turbine_type_map[:, ix_filter] if type(correct_cp_ct_for_tilt) is bool: pass @@ -363,6 +394,8 @@ def axial_induction( "yaw_angles": yaw_angles, "tilt_angles": tilt_angles, "power_setpoints": power_setpoints, + "awc_modes": awc_modes, + "awc_amplitudes": awc_amplitudes, "tilt_interp": tilt_interps[turb_type], "average_method": average_method, "cubature_weights": cubature_weights, diff --git a/floris/core/wake.py b/floris/core/wake.py index 2f9907c99..fe2fa9c50 100644 --- a/floris/core/wake.py +++ b/floris/core/wake.py @@ -73,6 +73,7 @@ class WakeModelManager(BaseClass): model_strings: dict = field(converter=dict) enable_secondary_steering: bool = field(converter=bool) enable_yaw_added_recovery: bool = field(converter=bool) + enable_active_wake_mixing: bool = field(converter=bool) enable_transverse_velocities: bool = field(converter=bool) wake_deflection_parameters: dict = field(converter=dict) diff --git a/floris/core/wake_velocity/empirical_gauss.py b/floris/core/wake_velocity/empirical_gauss.py index 722771012..4d8005056 100644 --- a/floris/core/wake_velocity/empirical_gauss.py +++ b/floris/core/wake_velocity/empirical_gauss.py @@ -59,6 +59,9 @@ class EmpiricalGaussVelocityDeficit(BaseModel): sigma_0_D: float = field(default=0.28) smoothing_length_D: float = field(default=2.0) mixing_gain_velocity: float = field(default=2.0) + awc_mode: str = field(default="baseline") + awc_wake_exp: float = field(default=1.2) + awc_wake_denominator: float = field(default=400) def prepare_function( self, @@ -281,3 +284,22 @@ def empirical_gauss_model_wake_width( sigmoid_integral(x, center=b, width=smoothing_length) return sigma + +def awc_added_wake_mixing( + awc_mode_i, + awc_amplitude_i, + awc_frequency_i, + awc_wake_exp, + awc_wake_denominator +): + + ## TODO: Add TI in the mix, finetune amplitude/freq effect + if (awc_mode_i == "helix").any(): + return awc_amplitude_i[:,:,0,0]**awc_wake_exp/awc_wake_denominator + elif (awc_mode_i == "baseline").any(): + return 0 + else: + raise NotImplementedError( + 'Active wake mixing strategies other than the `helix` mode ' + 'have not yet been implemented in FLORIS.' + ) diff --git a/floris/floris_model.py b/floris/floris_model.py index 78f60ae5f..709c06884 100644 --- a/floris/floris_model.py +++ b/floris/floris_model.py @@ -29,6 +29,7 @@ floris_array_converter, NDArrayBool, NDArrayFloat, + NDArrayStr, ) from floris.utilities import ( nested_get, @@ -233,6 +234,9 @@ def set_operation( self, yaw_angles: NDArrayFloat | list[float] | None = None, power_setpoints: NDArrayFloat | list[float] | list[float, None] | None = None, + awc_modes: NDArrayStr | list[str] | list[str, None] | None = None, + awc_amplitudes: NDArrayFloat | list[float] | list[float, None] | None = None, + awc_frequencies: NDArrayFloat | list[float] | list[float, None] | None = None, disable_turbines: NDArrayBool | list[bool] | None = None, ): """ @@ -274,6 +278,32 @@ def set_operation( self.core.farm.set_power_setpoints(power_setpoints) + if awc_modes is None: + awc_modes = np.array( + [["baseline"] + *self.core.farm.n_turbines] + *self.core.flow_field.n_findex + ) + self.core.farm.awc_modes = awc_modes + + if awc_amplitudes is None: + awc_amplitudes = np.zeros( + ( + self.core.flow_field.n_findex, + self.core.farm.n_turbines, + ) + ) + self.core.farm.awc_amplitudes = awc_amplitudes + + if awc_frequencies is None: + awc_frequencies = np.zeros( + ( + self.core.flow_field.n_findex, + self.core.farm.n_turbines, + ) + ) + self.core.farm.awc_frequencies = awc_frequencies + # Check for turbines to disable if disable_turbines is not None: @@ -322,6 +352,9 @@ def set( wind_data: type[WindDataBase] | None = None, yaw_angles: NDArrayFloat | list[float] | None = None, power_setpoints: NDArrayFloat | list[float] | list[float, None] | None = None, + awc_modes: NDArrayStr | list[str] | list[str, None] | None = None, + awc_amplitudes: NDArrayFloat | list[float] | list[float, None] | None = None, + awc_frequencies: NDArrayFloat | list[float] | list[float, None] | None = None, disable_turbines: NDArrayBool | list[bool] | None = None, ): """ @@ -360,6 +393,9 @@ def set( # Initialize a new Floris object after saving the setpoints _yaw_angles = self.core.farm.yaw_angles _power_setpoints = self.core.farm.power_setpoints + _awc_modes = self.core.farm.awc_modes + _awc_amplitudes = self.core.farm.awc_amplitudes + _awc_frequencies = self.core.farm.awc_frequencies self._reinitialize( wind_speeds=wind_speeds, wind_directions=wind_directions, @@ -386,11 +422,20 @@ def set( | (_power_setpoints == POWER_SETPOINT_DISABLED) ).all(): self.core.farm.set_power_setpoints(_power_setpoints) + if _awc_modes is not None: + self.core.farm.set_awc_modes(_awc_modes) + if not (_awc_amplitudes == 0).all(): + self.core.farm.set_awc_amplitudes(_awc_amplitudes) + if not (_awc_frequencies == 0).all(): + self.core.farm.set_awc_frequencies(_awc_frequencies) # Set the operation self.set_operation( yaw_angles=yaw_angles, power_setpoints=power_setpoints, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes, + awc_frequencies=awc_frequencies, disable_turbines=disable_turbines, ) @@ -451,6 +496,8 @@ def _get_turbine_powers(self) -> NDArrayFloat: yaw_angles=self.core.farm.yaw_angles, tilt_angles=self.core.farm.tilt_angles, power_setpoints=self.core.farm.power_setpoints, + awc_modes = self.core.farm.awc_modes, + awc_amplitudes=self.core.farm.awc_amplitudes, tilt_interps=self.core.farm.turbine_tilt_interps, turbine_type_map=self.core.farm.turbine_type_map, turbine_power_thrust_tables=self.core.farm.turbine_power_thrust_tables, @@ -862,6 +909,8 @@ def get_turbine_ais(self) -> NDArrayFloat: yaw_angles=self.core.farm.yaw_angles, tilt_angles=self.core.farm.tilt_angles, power_setpoints=self.core.farm.power_setpoints, + awc_modes = self.core.farm.awc_modes, + awc_amplitudes=self.core.farm.awc_amplitudes, axial_induction_functions=self.core.farm.turbine_axial_induction_functions, tilt_interps=self.core.farm.turbine_tilt_interps, correct_cp_ct_for_tilt=self.core.farm.correct_cp_ct_for_tilt, @@ -880,6 +929,8 @@ def get_turbine_thrust_coefficients(self) -> NDArrayFloat: yaw_angles=self.core.farm.yaw_angles, tilt_angles=self.core.farm.tilt_angles, power_setpoints=self.core.farm.power_setpoints, + awc_modes = self.core.farm.awc_modes, + awc_amplitudes=self.core.farm.awc_amplitudes, thrust_coefficient_functions=self.core.farm.turbine_thrust_coefficient_functions, tilt_interps=self.core.farm.turbine_tilt_interps, correct_cp_ct_for_tilt=self.core.farm.correct_cp_ct_for_tilt, @@ -909,6 +960,9 @@ def calculate_cross_plane( ti=None, yaw_angles=None, power_setpoints=None, + awc_modes=None, + awc_amplitudes=None, + awc_frequencies=None, disable_turbines=None, ): """ @@ -958,6 +1012,9 @@ def calculate_cross_plane( solver_settings=solver_settings, yaw_angles=yaw_angles, power_setpoints=power_setpoints, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes, + awc_frequencies=awc_frequencies, disable_turbines=disable_turbines, ) @@ -966,7 +1023,7 @@ def calculate_cross_plane( # Get the points of data in a dataframe # TODO this just seems to be flattening and storing the data in a df; is this necessary? - # It seems the biggest depenedcy is on CutPlane and the subsequent visualization tools. + # It seems the biggest dependency is on CutPlane and the subsequent visualization tools. df = self.get_plane_of_points( normal_vector="x", planar_coordinate=downstream_dist, @@ -995,6 +1052,9 @@ def calculate_horizontal_plane( ti=None, yaw_angles=None, power_setpoints=None, + awc_modes=None, + awc_amplitudes=None, + awc_frequencies=None, disable_turbines=None, ): """ @@ -1052,6 +1112,9 @@ def calculate_horizontal_plane( solver_settings=solver_settings, yaw_angles=yaw_angles, power_setpoints=power_setpoints, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes, + awc_frequencies=awc_frequencies, disable_turbines=disable_turbines, ) @@ -1094,6 +1157,9 @@ def calculate_y_plane( ti=None, yaw_angles=None, power_setpoints=None, + awc_modes=None, + awc_amplitudes=None, + awc_frequencies=None, disable_turbines=None, ): """ @@ -1156,6 +1222,9 @@ def calculate_y_plane( solver_settings=solver_settings, yaw_angles=yaw_angles, power_setpoints=power_setpoints, + awc_modes=awc_modes, + awc_amplitudes=awc_amplitudes, + awc_frequencies=awc_frequencies, disable_turbines=disable_turbines, ) diff --git a/floris/turbine_library/iea_10MW.yaml b/floris/turbine_library/iea_10MW.yaml index 28e504e6c..f68278a70 100644 --- a/floris/turbine_library/iea_10MW.yaml +++ b/floris/turbine_library/iea_10MW.yaml @@ -11,6 +11,11 @@ power_thrust_table: ref_tilt: 6.0 cosine_loss_exponent_yaw: 1.88 cosine_loss_exponent_tilt: 1.88 + helix_a: 1.719 + helix_power_b: 4.823e-03 + helix_power_c: 2.314e-10 + helix_thrust_b: 1.157e-03 + helix_thrust_c: 1.167e-04 power: - 0.0 - 0.0 diff --git a/floris/turbine_library/iea_15MW.yaml b/floris/turbine_library/iea_15MW.yaml index f72003404..6274b5f49 100644 --- a/floris/turbine_library/iea_15MW.yaml +++ b/floris/turbine_library/iea_15MW.yaml @@ -13,6 +13,11 @@ power_thrust_table: ref_tilt: 6.0 cosine_loss_exponent_yaw: 1.88 cosine_loss_exponent_tilt: 1.88 + helix_a: 1.809 + helix_power_b: 4.828e-03 + helix_power_c: 4.017e-11 + helix_thrust_b: 1.390e-03 + helix_thrust_c: 5.084e-04 power: - 0.000000 - 0.000000 diff --git a/floris/turbine_library/nrel_5MW.yaml b/floris/turbine_library/nrel_5MW.yaml index ce0c788f7..951441a61 100644 --- a/floris/turbine_library/nrel_5MW.yaml +++ b/floris/turbine_library/nrel_5MW.yaml @@ -39,6 +39,12 @@ power_thrust_table: cosine_loss_exponent_tilt: 1.88 # Cosine exponent for power loss due to yaw misalignment. cosine_loss_exponent_yaw: 1.88 + # Helix parameters + helix_a: 1.802 + helix_power_b: 4.568e-03 + helix_power_c: 1.629e-10 + helix_thrust_b: 1.027e-03 + helix_thrust_c: 1.378e-06 ### Power thrust table data wind_speed: - 0.0 diff --git a/floris/type_dec.py b/floris/type_dec.py index 2afbf7c9c..319a09917 100644 --- a/floris/type_dec.py +++ b/floris/type_dec.py @@ -27,6 +27,7 @@ NDArrayFilter = Union[npt.NDArray[np.int_], npt.NDArray[np.bool_]] NDArrayObject = npt.NDArray[np.object_] NDArrayBool = npt.NDArray[np.bool_] +NDArrayStr = npt.NDArray[np.str_] ### Custom callables for attrs objects and functions diff --git a/tests/conftest.py b/tests/conftest.py index b8b70dc7d..8a647dbd5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -211,6 +211,11 @@ def __init__(self): "cosine_loss_exponent_tilt": 1.88, "ref_air_density": 1.225, "ref_tilt": 5.0, + "helix_a": 1.802, + "helix_power_b": 4.568e-03, + "helix_power_c": 1.629e-10, + "helix_thrust_b": 1.027e-03, + "helix_thrust_c": 1.378e-06, "power": [ 0.0, 0.0, @@ -488,7 +493,9 @@ def __init__(self): "breakpoints_D": [10], "sigma_0_D": 0.28, "smoothing_length_D": 2.0, - "mixing_gain_velocity": 2.0 + "mixing_gain_velocity": 2.0, + "awc_wake_exp": 1.2, + "awc_wake_denominator": 400 }, }, "wake_turbulence_parameters": { @@ -504,6 +511,7 @@ def __init__(self): }, "enable_secondary_steering": False, "enable_yaw_added_recovery": False, + "enable_active_wake_mixing": False, "enable_transverse_velocities": False, } diff --git a/tests/data/input_full.yaml b/tests/data/input_full.yaml index d9415db1f..f3235b581 100644 --- a/tests/data/input_full.yaml +++ b/tests/data/input_full.yaml @@ -44,6 +44,7 @@ wake: enable_secondary_steering: true enable_yaw_added_recovery: true + enable_active_wake_mixing: true enable_transverse_velocities: true wake_deflection_parameters: diff --git a/tests/farm_unit_test.py b/tests/farm_unit_test.py index 38d2b91a7..3c8893998 100644 --- a/tests/farm_unit_test.py +++ b/tests/farm_unit_test.py @@ -50,6 +50,9 @@ def test_asdict(sample_inputs_fixture: SampleInputs): farm.set_yaw_angles_to_ref_yaw(N_FINDEX) farm.set_tilt_to_ref_tilt(N_FINDEX) farm.set_power_setpoints_to_ref_power(N_FINDEX) + farm.set_awc_modes_to_ref_mode(N_FINDEX) + farm.set_awc_amplitudes_to_ref_amp(N_FINDEX) + farm.set_awc_frequencies_to_ref_freq(N_FINDEX) dict1 = farm.as_dict() new_farm = farm.from_dict(dict1) @@ -58,6 +61,9 @@ def test_asdict(sample_inputs_fixture: SampleInputs): new_farm.set_yaw_angles_to_ref_yaw(N_FINDEX) new_farm.set_tilt_to_ref_tilt(N_FINDEX) new_farm.set_power_setpoints_to_ref_power(N_FINDEX) + new_farm.set_awc_modes_to_ref_mode(N_FINDEX) + new_farm.set_awc_amplitudes_to_ref_amp(N_FINDEX) + new_farm.set_awc_frequencies_to_ref_freq(N_FINDEX) dict2 = new_farm.as_dict() assert dict1 == dict2 diff --git a/tests/reg_tests/cumulative_curl_regression_test.py b/tests/reg_tests/cumulative_curl_regression_test.py index 8d47d0ebd..6de08a83b 100644 --- a/tests/reg_tests/cumulative_curl_regression_test.py +++ b/tests/reg_tests/cumulative_curl_regression_test.py @@ -204,6 +204,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -215,6 +217,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -228,6 +232,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -238,6 +244,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -366,6 +374,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -377,6 +387,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -390,6 +402,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -400,6 +414,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -455,6 +471,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -466,6 +484,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -479,6 +499,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -489,6 +511,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -543,6 +567,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -554,6 +580,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -567,6 +595,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -577,6 +607,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -645,6 +677,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes farm_powers = power( velocities, @@ -653,6 +687,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, diff --git a/tests/reg_tests/empirical_gauss_regression_test.py b/tests/reg_tests/empirical_gauss_regression_test.py index 224eb66de..c614fa633 100644 --- a/tests/reg_tests/empirical_gauss_regression_test.py +++ b/tests/reg_tests/empirical_gauss_regression_test.py @@ -110,6 +110,35 @@ ] ) +helix_added_recovery_baseline = np.array( + [ + # 8 m/s + [ + [7.9736858, 0.7871515, 1753954.4591792, 0.2693224], + [5.8181628, 0.8711866, 676912.0380737, 0.3205471], + [5.8941747, 0.8668654, 702276.3178047, 0.3175620], + ], + # 9 m/s + [ + [8.9703965, 0.7858774, 2496427.8618358, 0.2686331], + [6.5498312, 0.8358441, 984786.7218587, 0.2974192], + [6.6883370, 0.8295451, 1047057.3206209, 0.2935691], + ], + # 10 m/s + [ + [9.9671073, 0.7838789, 3417797.0050916, 0.2675559], + [7.2852518, 0.8049506, 1339238.8882972, 0.2791780], + [7.4865891, 0.7981254, 1452997.4778680, 0.2753477], + ], + # 11 m/s + [ + [10.9638180, 0.7565157, 4519404.3072862, 0.2532794], + [8.1286243, 0.7869622, 1867298.1260108, 0.2692199], + [8.2872457, 0.7867578, 1985849.6635654, 0.2691092], + ], + ] +) + full_flow_baseline = np.array( [ [ @@ -178,6 +207,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -189,6 +220,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -202,6 +235,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -212,6 +247,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -342,6 +379,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -353,6 +392,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -366,6 +407,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -376,6 +419,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -431,6 +476,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -442,6 +489,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -455,6 +504,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -465,6 +516,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -503,6 +556,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -514,6 +569,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -527,6 +584,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -537,6 +596,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -561,6 +622,98 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): assert_results_arrays(test_results[0:4], yaw_added_recovery_baseline) +def test_regression_helix(sample_inputs_fixture): + """ + Tandem turbines with the upstream turbine applying the helix + """ + sample_inputs_fixture.core["wake"]["model_strings"]["velocity_model"] = VELOCITY_MODEL + sample_inputs_fixture.core["wake"]["model_strings"]["deflection_model"] = DEFLECTION_MODEL + sample_inputs_fixture.core["wake"]["model_strings"]["turbulence_model"] = TURBULENCE_MODEL + + floris = Core.from_dict(sample_inputs_fixture.core) + + awc_modes = np.array([["helix"]*N_TURBINES]*N_FINDEX) + awc_amplitudes = np.zeros((N_FINDEX, N_TURBINES)) + awc_amplitudes[:,0] = 5.0 + floris.farm.awc_amplitudes = awc_amplitudes + + floris.initialize_domain() + floris.steady_state_atmospheric_condition() + + n_turbines = floris.farm.n_turbines + n_findex = floris.flow_field.n_findex + + velocities = floris.flow_field.u + air_density = floris.flow_field.air_density + yaw_angles = floris.farm.yaw_angles + tilt_angles = floris.farm.tilt_angles + power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes + test_results = np.zeros((n_findex, n_turbines, 4)) + + farm_avg_velocities = average_velocity( + velocities, + ) + farm_cts = thrust_coefficient( + velocities, + air_density, + yaw_angles, + tilt_angles, + power_setpoints, + awc_modes, + awc_amplitudes, + floris.farm.turbine_thrust_coefficient_functions, + floris.farm.turbine_tilt_interps, + floris.farm.correct_cp_ct_for_tilt, + floris.farm.turbine_type_map, + floris.farm.turbine_power_thrust_tables, + ) + farm_powers = power( + velocities, + air_density, + floris.farm.turbine_power_functions, + yaw_angles, + tilt_angles, + power_setpoints, + awc_modes, + awc_amplitudes, + floris.farm.turbine_tilt_interps, + floris.farm.turbine_type_map, + floris.farm.turbine_power_thrust_tables, + ) + farm_axial_inductions = axial_induction( + velocities, + air_density, + yaw_angles, + tilt_angles, + power_setpoints, + awc_modes, + awc_amplitudes, + floris.farm.turbine_axial_induction_functions, + floris.farm.turbine_tilt_interps, + floris.farm.correct_cp_ct_for_tilt, + floris.farm.turbine_type_map, + floris.farm.turbine_power_thrust_tables, + ) + for i in range(n_findex): + for j in range(n_turbines): + test_results[i, j, 0] = farm_avg_velocities[i, j] + test_results[i, j, 1] = farm_cts[i, j] + test_results[i, j, 2] = farm_powers[i, j] + test_results[i, j, 3] = farm_axial_inductions[i, j] + + if DEBUG: + print_test_values( + farm_avg_velocities, + farm_cts, + farm_powers, + farm_axial_inductions, + max_findex_print=4 + ) + + assert_results_arrays(test_results[0:4], helix_added_recovery_baseline) + def test_regression_small_grid_rotation(sample_inputs_fixture): """ @@ -623,6 +776,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): floris.farm.yaw_angles, floris.farm.tilt_angles, floris.farm.power_setpoints, + floris.farm.awc_modes, + floris.farm.awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, diff --git a/tests/reg_tests/gauss_regression_test.py b/tests/reg_tests/gauss_regression_test.py index bc876006b..cd3dcce0b 100644 --- a/tests/reg_tests/gauss_regression_test.py +++ b/tests/reg_tests/gauss_regression_test.py @@ -296,6 +296,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -307,6 +309,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -320,6 +324,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -330,6 +336,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -459,6 +467,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -470,6 +480,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -483,6 +495,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -493,6 +507,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -545,6 +561,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -556,6 +574,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -569,6 +589,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -579,6 +601,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -626,6 +650,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -637,6 +663,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -650,6 +678,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -660,6 +690,8 @@ def test_regression_gch(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -715,6 +747,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -726,6 +760,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -739,6 +775,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -749,6 +787,8 @@ def test_regression_yaw_added_recovery(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -803,6 +843,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -814,6 +856,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -827,6 +871,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -837,6 +883,8 @@ def test_regression_secondary_steering(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -904,6 +952,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes farm_powers = power( velocities, @@ -912,6 +962,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, diff --git a/tests/reg_tests/jensen_jimenez_regression_test.py b/tests/reg_tests/jensen_jimenez_regression_test.py index 775687077..8c6a2accd 100644 --- a/tests/reg_tests/jensen_jimenez_regression_test.py +++ b/tests/reg_tests/jensen_jimenez_regression_test.py @@ -146,6 +146,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -157,6 +159,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -170,6 +174,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -180,6 +186,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -308,6 +316,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -319,6 +329,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -332,6 +344,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -342,6 +356,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -410,6 +426,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes # farm_eff_velocities = rotor_effective_velocity( # floris.flow_field.air_density, @@ -431,6 +449,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, diff --git a/tests/reg_tests/none_regression_test.py b/tests/reg_tests/none_regression_test.py index aff811938..d8b7e87f3 100644 --- a/tests/reg_tests/none_regression_test.py +++ b/tests/reg_tests/none_regression_test.py @@ -147,6 +147,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -158,6 +160,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -171,6 +175,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -181,6 +187,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -346,6 +354,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes farm_powers = power( velocities, @@ -354,6 +364,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, diff --git a/tests/reg_tests/turbopark_regression_test.py b/tests/reg_tests/turbopark_regression_test.py index d4ee6febe..397a8586c 100644 --- a/tests/reg_tests/turbopark_regression_test.py +++ b/tests/reg_tests/turbopark_regression_test.py @@ -106,6 +106,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -117,6 +119,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -130,6 +134,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -140,6 +146,8 @@ def test_regression_tandem(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -269,6 +277,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes test_results = np.zeros((n_findex, n_turbines, 4)) farm_avg_velocities = average_velocity( @@ -280,6 +290,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_thrust_coefficient_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -293,6 +305,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, @@ -303,6 +317,8 @@ def test_regression_yaw(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_axial_induction_functions, floris.farm.turbine_tilt_interps, floris.farm.correct_cp_ct_for_tilt, @@ -365,6 +381,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles = floris.farm.yaw_angles tilt_angles = floris.farm.tilt_angles power_setpoints = floris.farm.power_setpoints + awc_modes = floris.farm.awc_modes + awc_amplitudes = floris.farm.awc_amplitudes farm_powers = power( velocities, @@ -373,6 +391,8 @@ def test_regression_small_grid_rotation(sample_inputs_fixture): yaw_angles, tilt_angles, power_setpoints, + awc_modes, + awc_amplitudes, floris.farm.turbine_tilt_interps, floris.farm.turbine_type_map, floris.farm.turbine_power_thrust_tables, diff --git a/tests/turbine_multi_dim_unit_test.py b/tests/turbine_multi_dim_unit_test.py index 55b582e41..8a429a74c 100644 --- a/tests/turbine_multi_dim_unit_test.py +++ b/tests/turbine_multi_dim_unit_test.py @@ -85,7 +85,9 @@ def test_ct(): air_density=None, yaw_angles=np.zeros((1, 1)), tilt_angles=np.ones((1, 1)) * 5.0, - power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT,\ + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, 1)), thrust_coefficient_functions={turbine.turbine_type: turbine.thrust_coefficient_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False]]), @@ -104,6 +106,8 @@ def test_ct(): yaw_angles=np.zeros((1, N_TURBINES)), tilt_angles=np.ones((1, N_TURBINES)) * 5.0, power_setpoints=np.ones((1, N_TURBINES)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, N_TURBINES)), thrust_coefficient_functions={turbine.turbine_type: turbine.thrust_coefficient_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False] * N_TURBINES]), @@ -156,6 +160,8 @@ def test_power(): yaw_angles=np.zeros((1, 1)), # 1 findex, 1 turbine tilt_angles=turbine.power_thrust_table[condition]["ref_tilt"] * np.ones((1, 1)), power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, 1)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map[:,0], turbine_power_thrust_tables={turbine.turbine_type: turbine.power_thrust_table}, @@ -175,6 +181,8 @@ def test_power(): yaw_angles=np.zeros((1, N_TURBINES)), tilt_angles=np.ones((1, N_TURBINES)) * 5.0, power_setpoints=np.ones((1, N_TURBINES)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, N_TURBINES)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map, ix_filter=INDEX_FILTER, @@ -214,6 +222,8 @@ def test_axial_induction(): yaw_angles=np.zeros((1, 1)), tilt_angles=np.ones((1, 1)) * 5.0, power_setpoints = np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, 1)), axial_induction_functions={turbine.turbine_type: turbine.axial_induction_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False]]), @@ -230,6 +240,8 @@ def test_axial_induction(): yaw_angles=np.zeros((1, N_TURBINES)), tilt_angles=np.ones((1, N_TURBINES)) * 5.0, power_setpoints=np.ones((1, N_TURBINES)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, N_TURBINES)), axial_induction_functions={turbine.turbine_type: turbine.axial_induction_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False] * N_TURBINES]), diff --git a/tests/turbine_operation_models_integration_test.py b/tests/turbine_operation_models_integration_test.py index 4732bd555..bd9dc3930 100644 --- a/tests/turbine_operation_models_integration_test.py +++ b/tests/turbine_operation_models_integration_test.py @@ -2,6 +2,7 @@ import pytest from floris.core.turbine.operation_models import ( + AWCTurbine, CosineLossTurbine, MixedOperationTurbine, POWER_SETPOINT_DEFAULT, @@ -49,6 +50,10 @@ def test_submodel_attributes(): assert hasattr(MixedOperationTurbine, "thrust_coefficient") assert hasattr(MixedOperationTurbine, "axial_induction") + assert hasattr(AWCTurbine, "power") + assert hasattr(AWCTurbine, "thrust_coefficient") + assert hasattr(AWCTurbine, "axial_induction") + def test_SimpleTurbine(): n_turbines = 1 @@ -228,12 +233,14 @@ def test_CosineLossTurbine(): absolute_tilt = tilt_angles_test - turbine_data["power_thrust_table"]["ref_tilt"] assert test_Ct == baseline_Ct * cosd(yaw_angles_test) * cosd(absolute_tilt) + def test_SimpleDeratingTurbine(): n_turbines = 1 wind_speed = 10.0 turbine_data = SampleInputs().turbine + # Check that for no specified derating, matches SimpleTurbine test_Ct = SimpleDeratingTurbine.thrust_coefficient( power_thrust_table=turbine_data["power_thrust_table"], @@ -498,3 +505,79 @@ def test_MixedOperationTurbine(): tilt_angles=tilt_angles_nom, tilt_interp=None ) + +def test_AWCTurbine(): + + n_turbines = 1 + wind_speed = 10.0 + turbine_data = SampleInputs().turbine + + # Baseline + base_Ct = SimpleTurbine.thrust_coefficient( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + ) + base_power = SimpleTurbine.power( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + air_density=turbine_data["power_thrust_table"]["ref_air_density"], + ) + base_ai = SimpleTurbine.axial_induction( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + ) + + # Test no change to Ct, power, or ai when helix amplitudes are 0 + test_Ct = AWCTurbine.thrust_coefficient( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + awc_modes=np.array([["helix"]*n_turbines]*1), + awc_amplitudes=np.zeros((1, n_turbines)), + ) + assert np.allclose(test_Ct, base_Ct) + + test_power = AWCTurbine.power( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + air_density=turbine_data["power_thrust_table"]["ref_air_density"], + awc_modes=np.array([["helix"]*n_turbines]*1), + awc_amplitudes=np.zeros((1, n_turbines)), + ) + assert np.allclose(test_power, base_power) + + test_ai = AWCTurbine.axial_induction( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + awc_modes=np.array([["helix"]*n_turbines]*1), + awc_amplitudes=np.zeros((1, n_turbines)), + ) + assert np.allclose(test_ai, base_ai) + + # Test that Ct, power, and ai all decrease when helix amplitudes are non-zero + test_Ct = AWCTurbine.thrust_coefficient( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + awc_modes=np.array([["helix"]*n_turbines]*1), + awc_amplitudes=2*np.ones((1, n_turbines)), + ) + assert test_Ct < base_Ct + assert test_Ct > 0 + + test_power = AWCTurbine.power( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + air_density=turbine_data["power_thrust_table"]["ref_air_density"], + awc_modes=np.array([["helix"]*n_turbines]*1), + awc_amplitudes=2*np.ones((1, n_turbines)), + ) + assert test_power < base_power + assert test_power > 0 + + test_ai = AWCTurbine.axial_induction( + power_thrust_table=turbine_data["power_thrust_table"], + velocities=wind_speed * np.ones((1, n_turbines, 3, 3)), # 1 findex, 1 turbine, 3x3 grid + awc_modes=np.array([["helix"]*n_turbines]*1), + awc_amplitudes=2*np.ones((1, n_turbines)), + ) + assert test_ai < base_ai + assert test_ai > 0 diff --git a/tests/turbine_unit_test.py b/tests/turbine_unit_test.py index 2ef7a7d97..2161a7309 100644 --- a/tests/turbine_unit_test.py +++ b/tests/turbine_unit_test.py @@ -183,6 +183,8 @@ def test_ct(): yaw_angles=np.zeros((1, 1)), tilt_angles=np.ones((1, 1)) * 5.0, power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), thrust_coefficient_functions={turbine.turbine_type: turbine.thrust_coefficient_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False]]), @@ -204,6 +206,8 @@ def test_ct(): yaw_angles=np.zeros((1, N_TURBINES)), tilt_angles=np.ones((1, N_TURBINES)) * 5.0, power_setpoints=np.ones((1, N_TURBINES)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, N_TURBINES)), thrust_coefficient_functions={turbine.turbine_type: turbine.thrust_coefficient_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False] * N_TURBINES]), @@ -227,6 +231,8 @@ def test_ct(): yaw_angles=np.zeros((1, 1)), tilt_angles=np.ones((1, 1)) * 5.0, power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), thrust_coefficient_functions={ turbine.turbine_type: turbine_floating.thrust_coefficient_function }, @@ -259,6 +265,8 @@ def test_power(): power_functions={turbine.turbine_type: turbine.power_function}, yaw_angles=np.zeros((1, 1)), # 1 findex, 1 turbine power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), tilt_angles=turbine.power_thrust_table["ref_tilt"] * np.ones((1, 1)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map[:,0], @@ -280,6 +288,8 @@ def test_power(): yaw_angles=np.zeros((1, 1)), # 1 findex, 1 turbine tilt_angles=turbine.power_thrust_table["ref_tilt"] * np.ones((1, 1)), power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map[:,0], turbine_power_thrust_tables={turbine.turbine_type: turbine.power_thrust_table}, @@ -296,6 +306,8 @@ def test_power(): yaw_angles=np.zeros((1, 1)), # 1 findex, 1 turbine tilt_angles=turbine.power_thrust_table["ref_tilt"] * np.ones((1, 1)), power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map[:,0], turbine_power_thrust_tables={turbine.turbine_type: turbine.power_thrust_table}, @@ -317,6 +329,8 @@ def test_power(): yaw_angles=np.zeros((1, n_turbines)), tilt_angles=turbine.power_thrust_table["ref_tilt"] * np.ones((1, n_turbines)), power_setpoints=np.ones((1, n_turbines)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*n_turbines]*1), + awc_amplitudes=np.zeros((1, n_turbines)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map, turbine_power_thrust_tables={turbine.turbine_type: turbine.power_thrust_table}, @@ -338,6 +352,8 @@ def test_power(): yaw_angles=np.zeros((1, n_turbines)), tilt_angles=turbine.power_thrust_table["ref_tilt"] * np.ones((1, n_turbines)), power_setpoints=np.ones((1, n_turbines)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*n_turbines]*1), + awc_amplitudes=np.zeros((1, n_turbines)), tilt_interps={turbine.turbine_type: turbine.tilt_interp}, turbine_type_map=turbine_type_map, turbine_power_thrust_tables={turbine.turbine_type: turbine.power_thrust_table}, @@ -368,6 +384,8 @@ def test_axial_induction(): yaw_angles=np.zeros((1, 1)), tilt_angles=np.ones((1, 1)) * 5.0, power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), axial_induction_functions={turbine.turbine_type: turbine.axial_induction_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False]]), @@ -383,6 +401,8 @@ def test_axial_induction(): yaw_angles=np.zeros((1, N_TURBINES)), tilt_angles=np.ones((1, N_TURBINES)) * 5.0, power_setpoints=np.ones((1, N_TURBINES)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array([["baseline"]*N_TURBINES]*1), + awc_amplitudes=np.zeros((1, N_TURBINES)), axial_induction_functions={turbine.turbine_type: turbine.axial_induction_function}, tilt_interps={turbine.turbine_type: None}, correct_cp_ct_for_tilt=np.array([[False] * N_TURBINES]), @@ -403,6 +423,8 @@ def test_axial_induction(): yaw_angles=np.zeros((1, 1)), tilt_angles=np.ones((1, 1)) * 5.0, power_setpoints=np.ones((1, 1)) * POWER_SETPOINT_DEFAULT, + awc_modes=np.array("baseline"), + awc_amplitudes=np.zeros((1, 1)), axial_induction_functions={turbine.turbine_type: turbine.axial_induction_function}, tilt_interps={turbine_floating.turbine_type: turbine_floating.tilt_interp}, correct_cp_ct_for_tilt=np.array([[True]]),