Source code for floris.cut_plane


import copy

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy.interpolate import griddata


[docs] def nudge_outward(x): """ Avoid numerical issue in grid data by slightly expanding input x. TODO: expand this description - What numerical issues? - Whats the scenario when I would need this? Args: x (np.array): Vector of values. Returns: np.array: Expanded vector. """ nudge_val = 0.001 min_x = np.min(x) max_x = np.max(x) x = np.where(x == min_x, min_x - nudge_val, x) x = np.where(x == max_x, max_x + nudge_val, x) return x
[docs] def get_plane_from_flow_data(flow_data, normal_vector="z", x3_value=100): """ Get a plane of data, in form of DataFrame, from a :py:class:`~.FlowData` object. This is used to get planes from SOWFA results and FLORIS simulations with fixed grids, i.e. curl. Args: flow_data (np.array): 3D vector field of velocity data. #TODO: is this supposed to be a :py:class:`~.FlowData` object? normal_vector (string, optional): Vector normal to plane. Defaults to z. x3_value (float, optional): Value of normal vector to slice through. Defaults to 100. Returns: pandas.DataFrame: Extracted data. """ order = "f" if normal_vector == "z": x1_array = flow_data.x.flatten(order=order) x2_array = flow_data.y.flatten(order=order) x3_array = flow_data.z.flatten(order=order) if normal_vector == "x": x3_array = flow_data.x.flatten(order=order) x1_array = flow_data.y.flatten(order=order) x2_array = flow_data.z.flatten(order=order) if normal_vector == "y": x3_array = flow_data.y.flatten(order=order) x1_array = flow_data.x.flatten(order=order) x2_array = flow_data.z.flatten(order=order) u = flow_data.u.flatten(order=order) v = flow_data.v.flatten(order=order) w = flow_data.w.flatten(order=order) search_values = np.array(sorted(np.unique(x3_array))) nearest_idx = (np.abs(search_values - x3_value)).argmin() nearest_value = search_values[nearest_idx] print("Nearest value to %.2f is %.2f" % (x3_value, nearest_value)) # Select down the data x3_select_mask = x3_array == nearest_value # Store the un-interpolated input arrays at this slice x1 = x1_array[x3_select_mask] x2 = x2_array[x3_select_mask] x3 = np.ones_like(x1) * x3_value u = u[x3_select_mask] v = v[x3_select_mask] w = w[x3_select_mask] df = pd.DataFrame({"x1": x1, "x2": x2, "x3": x3, "u": u, "v": v, "w": w}) return df
[docs] class CutPlane: """ A CutPlane object represents a 2D slice through the flow of a FLORIS simulation, or other such as SOWFA result. """ def __init__(self, df, x1_resolution, x2_resolution, normal_vector): """ Initialize CutPlane object, storing the DataFrame and resolution. Args: df (pandas.DataFrame): Pandas DataFrame of data with columns x1, x2, u, v, w. """ self.df: pd.DataFrame = df self.normal_vector: str = normal_vector self.resolution = (x1_resolution, x2_resolution) self.df.set_index(["x1", "x2"]) def __sub__(self, other): if self.normal_vector != other.normal_vector: raise ValueError("Operands must have consistent normal vectors.") # if self.normal_vector.df. # DF must be of the same size # resolution must be of the same size df: pd.DataFrame = self.df.copy() other_df: pd.DataFrame = other.df.copy() df['u'] = self.df['u'] - other_df['u'] df['v'] = self.df['v'] - other_df['v'] df['w'] = self.df['w'] - other_df['w'] return CutPlane( df, self.resolution[0], self.resolution[1], self.normal_vector )
# Modification functions
[docs] def set_origin(cut_plane, center_x1=0.0, center_x2=0.0): """ Establish the origin of a CutPlane object. Args: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Plane of data. center_x1 (float, optional): x1-coordinate of origin. Defaults to 0.0. center_x2 (float, optional): x2-coordinate of origin. Defaults to 0.0. Returns: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Updated plane of data. """ # Store the un-interpolated input arrays at this slice cut_plane.df.x1 = cut_plane.df.x1 - center_x1 cut_plane.df.x2 = cut_plane.df.x2 - center_x2 return cut_plane
[docs] def change_resolution(cut_plane, resolution=(100, 100)): """ Modify default resolution of a CutPlane object. Args: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Plane of data. resolution (tuple, optional): Desired resolution in x1 and x2. Defaults to (100, 100). Returns: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Updated plane of data. """ # Linearize the data x1_lin = np.linspace(min(cut_plane.df.x1), max(cut_plane.df.x1), resolution[0]) x2_lin = np.linspace(min(cut_plane.df.x2), max(cut_plane.df.x2), resolution[1]) # x3 = np.ones_like(x1) * cut_plane.df.x3[0] # Mesh the data x1_mesh, x2_mesh = np.meshgrid(x1_lin, x2_lin) x3_mesh = np.ones_like(x1_mesh) * cut_plane.df.x3[0] # Interpolate u,v,w u_mesh = griddata( np.column_stack( [nudge_outward(cut_plane.df.x1), nudge_outward(cut_plane.df.x2)] ), cut_plane.df.u.values, (x1_mesh.flatten(), x2_mesh.flatten()), method="cubic", ) v_mesh = griddata( np.column_stack( [nudge_outward(cut_plane.df.x1), nudge_outward(cut_plane.df.x2)] ), cut_plane.df.v.values, (x1_mesh.flatten(), x2_mesh.flatten()), method="cubic", ) w_mesh = griddata( np.column_stack( [nudge_outward(cut_plane.df.x1), nudge_outward(cut_plane.df.x2)] ), cut_plane.df.w.values, (x1_mesh.flatten(), x2_mesh.flatten()), method="cubic", ) # Assign back to df cut_plane.df = pd.DataFrame( { "x1": x1_mesh.flatten(), "x2": x2_mesh.flatten(), "x3": x3_mesh.flatten(), "u": u_mesh.flatten(), "v": v_mesh.flatten(), "w": w_mesh.flatten(), } ) # Save the resolution cut_plane.resolution = resolution # Return the cutplane return cut_plane
[docs] def interpolate_onto_array(cut_plane_in, x1_array, x2_array): """ Interpolate a CutPlane object onto specified coordinate arrays. Args: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Plane of data. x1_array (np.array): Specified x1-coordinate. x2_array (np.array): Specified x2-coordinate. Returns: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Updated plane of data. """ cut_plane = copy.deepcopy(cut_plane_in) # Linearize the data x1_lin = x1_array x2_lin = x2_array # Save the new resolution cut_plane.resolution = (len(np.unique(x1_lin)), len(np.unique(x2_lin))) # Mesh the data x1_mesh, x2_mesh = np.meshgrid(x1_lin, x2_lin) x3_mesh = np.ones_like(x1_mesh) * cut_plane.df.x3.iloc[0] # Interpolate u,v,w u_mesh = griddata( np.column_stack( [nudge_outward(cut_plane.df.x1), nudge_outward(cut_plane.df.x2)] ), cut_plane.df.u.values, (x1_mesh.flatten(), x2_mesh.flatten()), method="cubic", ) v_mesh = griddata( np.column_stack( [nudge_outward(cut_plane.df.x1), nudge_outward(cut_plane.df.x2)] ), cut_plane.df.v.values, (x1_mesh.flatten(), x2_mesh.flatten()), method="cubic", ) w_mesh = griddata( np.column_stack( [nudge_outward(cut_plane.df.x1), nudge_outward(cut_plane.df.x2)] ), cut_plane.df.w.values, (x1_mesh.flatten(), x2_mesh.flatten()), method="cubic", ) # Assign back to df cut_plane.df = pd.DataFrame( { "x1": x1_mesh.flatten(), "x2": x2_mesh.flatten(), "x3": x3_mesh.flatten(), "u": u_mesh.flatten(), "v": v_mesh.flatten(), "w": w_mesh.flatten(), } ) # Return the cutplane return cut_plane
[docs] def rescale_axis(cut_plane, x1_factor=1.0, x2_factor=1.0): """ Stretch or compress CutPlane coordinates. Args: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Plane of data. x1_factor (float): Scaling factor for x1-coordinate. x2_factor (float): Scaling factor for x2-coordinate. Returns: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Updated plane of data. """ # Store the un-interpolated input arrays at this slice cut_plane.df.x1 = cut_plane.df.x1 / x1_factor cut_plane.df.x2 = cut_plane.df.x2 / x2_factor return cut_plane
[docs] def project_onto(cut_plane_a, cut_plane_b): """ Project cut_plane_a onto the x1, x2 of cut_plane_b Args: cut_plane_a (:py:class:`~.tools.cut_plane.CutPlane`): Plane of data to project from. cut_plane_b (:py:class:`~.tools.cut_plane.CutPlane`): Plane of data to project onto. Returns: cut_plane (:py:class:`~.tools.cut_plane.CutPlane`): Cut_plane_a projected onto cut_plane_b's axis. """ return interpolate_onto_array( cut_plane_a, cut_plane_b.df.x1.unique(), cut_plane_b.df.x2.unique() )
[docs] def calculate_wind_speed(cross_plane, x1_loc, x2_loc, R): """ Calculate effective wind speed within specified range of a point. Args: cross_plane (:py:class:`floris.cut_plane.CrossPlane`): plane of data. x1_loc (float): x1-coordinate of point of interest. x2_loc (float): x2-coordinate of point of interest. R (float): radius from point of interest to consider Returns: (float): effective wind speed """ # Temp local copy df = cross_plane.df.copy() # Make a distance column df["distance"] = np.sqrt((df.x1 - x1_loc) ** 2 + (df.x2 - x2_loc) ** 2) # Return the cube-mean wind speed return np.cbrt(np.mean(df[df.distance < R].u ** 3))
[docs] def wind_speed_profile(cross_plane, R, x2_loc, resolution=100, x1_locs=None): if x1_locs is None: x1_locs = np.linspace( min(cross_plane.df.x1), max(cross_plane.df.x1), resolution ) v_array = np.array( [calculate_wind_speed(cross_plane, x1_loc, x2_loc, R) for x1_loc in x1_locs] ) return x1_locs, v_array
[docs] def calculate_power( cross_plane, x1_loc, x2_loc, R, ws_array, cp_array, air_density=1.225 ): """ Calculate maximum power available in a given cross plane. Args: cross_plane (:py:class:`floris.cut_plane.CrossPlane`): plane of data. x1_loc (float): x1-coordinate of point of interest. x2_loc (float): x2-coordinate of point of interest. R (float): Radius of wind turbine rotor. ws_array (np.array): reference wind speed for cp curve. cp_array (np.array): cp curve at reference wind speeds. air_density (float, optional): air density. Defaults to 1.225. Returns: float: Power! """ # Compute the ws ws = calculate_wind_speed(cross_plane, x1_loc, x2_loc, R) # Compute the cp cp_value = np.interp(ws, ws_array, cp_array) # Return the power return 0.5 * air_density * (np.pi * R ** 2) * cp_value * ws ** 3
[docs] def get_power_profile( cross_plane, x2_loc, ws_array, cp_array, R, air_density=1.225, resolution=100, x1_locs=None, ): if x1_locs is None: x1_locs = np.linspace( min(cross_plane.df.x1), max(cross_plane.df.x1), resolution ) p_array = np.array( [ calculate_power( cross_plane, x1_loc, x2_loc, R, ws_array, cp_array, air_density=air_density, ) for x1_loc in x1_locs ] ) return x1_locs, p_array
# # Define horizontal subclass # class HorPlane(_CutPlane): # """ # Subclass of _CutPlane. Shortcut to extracting a horizontal plane. # """ # def __init__(self, df): # """ # Initialize horizontal CutPlane # Args: # flow_data (np.array): 3D vector field of velocity data # z_value (float): vertical position through which to slice # """ # # Set up call super # super().__init__(df) # # Define cross plane subclass # class CrossPlane(_CutPlane): # """ # Subclass of _CutPlane. Shortcut to extracting a cross-stream plane. # """ # def __init__(self, df): # """ # Initialize cross-stream CutPlane # Args: # flow_data (np.array): 3D vector field of velocity data # x_value (float): streamwise position through which to slice # """ # # Set up call super # super().__init__(df) # # Define cross plane subclass # class VertPlane(_CutPlane): # """ # Subclass of _CutPlane. Shortcut to extracting a streamwise-vertical plane. # """ # def __init__(self, df): # """ # Initialize streamwise-vertical CutPlane # Args: # flow_data (np.array): 3D vector field of velocity data # y_value (float): spanwise position through which to slice # """ # # Set up call super # super().__init__(df)