Source code for floris.turbine_library.turbine_utilities


from __future__ import annotations

from collections.abc import Iterable

import numpy as np
import yaml


[docs] def build_cosine_loss_turbine_dict( turbine_data_dict, turbine_name, file_name=None, generator_efficiency=None, hub_height=90.0, cosine_loss_exponent_yaw=1.88, cosine_loss_exponent_tilt=1.88, rotor_diameter=125.88, TSR=8.0, ref_air_density=1.225, ref_tilt=5.0 ): """ Tool for formatting a full turbine dict from data formatted as a dictionary. Default value for turbine physical parameters are from the NREL 5MW reference wind turbine. Returns a turbine dictionary object as expected by FLORIS. Optionally, prints the dictionary to a yaml to be included in a FLORIS wake model yaml. turbine_data is a dictionary that contains keys specifying the turbine power and thrust as a function of wind speed. The following keys are possible: - wind_speed [m/s] - power [kW] - power_coefficient [-] - thrust [kN] - thrust_coefficient [-] Of these, wind_speed is required. One of power and power_coefficient must be specified; and one of thrust and thrust_coefficient must be specified. If both (absolute) and _coefficient versions are specified, the (absolute) power will be used along with the thrust_coefficient, with the other entries ignored. Args: turbine_data_dict (dict): Dictionary containing performance of the wind turbine as a function of wind speed. Described in more detail above. turbine_name (string): Name of the turbine, which will be used for the turbine_type field as well as the filename. file_name (): Name for the produced yaml, including possibly path. Defaults to None, in which case no yaml is written. generator_efficiency (float): Generator efficiency [-]. Unused if power is specified in absolute terms in the turbine_data_dict. Must be specified if power not specified and power_coefficient specified instead. Defaults to None. hub_height (float): Hub height [m]. Defaults to 90.0. cosine_loss_exponent_yaw (float): Cosine exponent for power loss to yaw [-]. Defaults to 1.88. cosine_loss_exponent_tilt (float): Cosine exponent for thrust loss to yaw [-]. Defaults to 1.88. rotor_diameter (float). Rotor diameter [m]. Defaults to 126.0. TSR (float). Turbine optimal tip-speed ratio [-]. Defaults to 8.0. ref_air_density (float). Air density used to specify power and thrust curves [kg/m^3]. Defaults to 1.225. ref_tilt (float). Rotor tilt (due to shaft tilt and/or platform tilt) used when defining the power and thrust curves [deg]. Defaults to 5.0. Returns: turbine_dict (dict): Formatted turbine dictionary as expected by FLORIS. """ # Check that necessary columns are specified if "wind_speed" not in turbine_data_dict: raise KeyError("wind_speed column must be specified.") u = np.array(turbine_data_dict["wind_speed"]) A = np.pi * rotor_diameter**2/4 # Construct the Cp curve if "power" in turbine_data_dict: if "power_coefficient" in turbine_data_dict: print( "Found both power and power_coefficient. " "Ignoring power_coefficient." ) p = np.array(turbine_data_dict["power"]) elif "power_coefficient" in turbine_data_dict: if generator_efficiency is None: raise KeyError( "generator_efficiency must be specified to convert power_coefficient to power." ) Cp = np.array(turbine_data_dict["power_coefficient"]) if _find_nearest_value_for_wind_speed(Cp, u, 10) > 16.0/27.0 or \ _find_nearest_value_for_wind_speed(Cp, u, 10) < 0.0: print( "Unusual power coefficient detected. Check that power coefficients" "are physical." ) validity_mask = (Cp != 0) | (u != 0) p = np.zeros_like(Cp, dtype=float) p[validity_mask] = ( Cp[validity_mask] * 0.5 * ref_air_density * A * generator_efficiency * u[validity_mask]**3 / 1000 ) else: raise KeyError( "Either power or power_coefficient must be specified." ) # Construct Ct curve if "thrust_coefficient" in turbine_data_dict: if "thrust" in turbine_data_dict: print( "Found both thrust and thrust_coefficient. " "Ignoring thrust." ) Ct = np.array(turbine_data_dict["thrust_coefficient"]) elif "thrust" in turbine_data_dict: T = np.array(turbine_data_dict["thrust"]) if _find_nearest_value_for_wind_speed(T, u, 10) > 3000 or \ _find_nearest_value_for_wind_speed(T, u, 10) < 100: print( "Unusual thrust value detected. Please check that thrust", "is specified in kN." ) validity_mask = (T != 0) | (u != 0) Ct = np.zeros_like(T) Ct[validity_mask] = (T[validity_mask]*1000)/\ (0.5*ref_air_density*A*u[validity_mask]**2) else: raise KeyError( "Either thrust or thrust_coefficient must be specified." ) # Build the turbine dict power_thrust_dict = { "ref_air_density": ref_air_density, "ref_tilt": ref_tilt, "cosine_loss_exponent_yaw": cosine_loss_exponent_yaw, "cosine_loss_exponent_tilt": cosine_loss_exponent_tilt, "wind_speed": u.tolist(), "power": p.tolist(), "thrust_coefficient": Ct.tolist() } turbine_dict = { "turbine_type": turbine_name, "hub_height": hub_height, "rotor_diameter": rotor_diameter, "TSR": TSR, "operation_model": "cosine-loss", "power_thrust_table": power_thrust_dict } # Create yaml file if file_name is not None: yaml.dump( turbine_dict, open(file_name, "w"), sort_keys=False ) print(file_name, "created.") return turbine_dict
def _find_nearest_value_for_wind_speed(test_vals, ws_vals, ws): errs = np.absolute(ws_vals-ws) idx = errs.argmin() return test_vals[idx]
[docs] def check_smooth_power_curve(power, tolerance=0.001): """ Check whether there are "wiggles" in the power signal. """ if power[-1] < 0.95*max(power): # Cut-out or shutdown included expected_changes = 2 else: # Shutdown appears not to be included expected_changes = 1 dirs = np.where( np.abs(np.diff(power)) > tolerance, np.sign(np.diff(power)), np.zeros(len(power)-1) ) dir_changes = np.sum(np.abs(np.diff(dirs))) is_smooth = dir_changes <= expected_changes return is_smooth