Skip to content

Commit

Permalink
Add z/3d to HeterogeneousMap (#915)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulf81 authored Jun 5, 2024
1 parent 7fa3bf1 commit 97946f9
Show file tree
Hide file tree
Showing 6 changed files with 561 additions and 28 deletions.
273 changes: 264 additions & 9 deletions docs/heterogeneous_map.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@
wind_directions=[270.0, 280.0],
)

# Print the HeterogeneousMap object
print(heterogeneous_map)

# Now create a new TimeSeries object including the heterogeneous_inflow_config_by_wd
time_series = TimeSeries(
wind_directions=np.array([269.0, 270.0, 271.0, 282.0]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,13 @@
# Note that we initialize FLORIS with a homogenous flow input file, but
# then configure the heterogeneous inflow via the reinitialize method.
fmodel_3d = FlorisModel("../inputs/gch.yaml")
fmodel_3d.set(heterogeneous_inflow_config=heterogeneous_inflow_config)

# Set shear to 0.0 to highlight the heterogeneous inflow
fmodel_3d.set(wind_shear=0.0)

# Apply the heterogeneous inflow configuration
fmodel_3d.set(heterogeneous_inflow_config=heterogeneous_inflow_config)

# Using the FlorisModel functions for generating plots, run FLORIS
# and extract 2D planes of data.
horizontal_plane_3d = fmodel_3d.calculate_horizontal_plane(
Expand Down
13 changes: 13 additions & 0 deletions floris/floris_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,19 @@ def _reinitialize(
if air_density is not None:
flow_field_dict["air_density"] = air_density
if heterogeneous_inflow_config is not None:
if (
"z" in heterogeneous_inflow_config
and flow_field_dict["wind_shear"] != 0.0
and heterogeneous_inflow_config['z'] is not None
):
raise ValueError(
"Heterogeneous inflow configuration contains a z term, and "
"flow_field_dict['wind_shear'] is not 0.0. Combining both options "
"is not currently allowed in FLORIS. If using a z term in the "
" heterogeneous inflow configuration, set flow_field_dict['wind_shear'] "
"to 0.0."
)

flow_field_dict["heterogeneous_inflow_config"] = heterogeneous_inflow_config

## Farm
Expand Down
134 changes: 116 additions & 18 deletions floris/heterogeneous_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class HeterogeneousMap(LoggingManager):
speed_multipliers (NDArrayFloat): A 2D NumPy array (size num_wd (or num_ws) x num_points)
of speed multipliers. If neither wind_directions nor wind_speeds are defined, then
this should be a single row array
z (NDArrayFloat, optional): A 1D NumPy array (size num_points) of z-coordinates (meters).
Optional.
wind_directions (NDArrayFloat, optional): A 1D NumPy array (size num_wd) of wind directions
(degrees). Optional.
wind_speeds (NDArrayFloat, optional): A 1D NumPy array (size num_ws) of wind speeds (m/s).
Expand All @@ -39,6 +41,7 @@ def __init__(
x: NDArrayFloat,
y: NDArrayFloat,
speed_multipliers: NDArrayFloat,
z: NDArrayFloat = None,
wind_directions: NDArrayFloat = None,
wind_speeds: NDArrayFloat = None,
):
Expand All @@ -50,20 +53,39 @@ def __init__(
if not isinstance(speed_multipliers, (list, np.ndarray)):
raise TypeError("speed_multipliers must be a numpy array or list")

Check warning on line 54 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L54

Added line #L54 was not covered by tests

# If z is provided, check that it is a list or numpy array
if (z is not None) and (not isinstance(z, (list, np.ndarray))):
raise TypeError("z must be a numpy array or list")

Check warning on line 58 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L58

Added line #L58 was not covered by tests

# Save the values
self.x = np.array(x)
self.y = np.array(y)
self.speed_multipliers = np.array(speed_multipliers)

# If z is provided, save it as an np array
if z is not None:
self.z = np.array(z)
else:
self.z = None

# Check that the length of the 1st dimension of speed_multipliers is the
# same as the length of both x and y
if (len(self.x) != self.speed_multipliers.shape[1]
or len(self.y) != self.speed_multipliers.shape[1]):
if (
len(self.x) != self.speed_multipliers.shape[1]
or len(self.y) != self.speed_multipliers.shape[1]
):
raise ValueError(

Check warning on line 77 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L77

Added line #L77 was not covered by tests
"The lengths of x and y must equal the 1th dimension of speed_multipliers "
"within the heterogeneous_inflow_config_by_wd dictionary"
)

# If z is provided, check that it is the same length as the 1st
# dimension of speed_multipliers
if self.z is not None:
if len(self.z) != self.speed_multipliers.shape[1]:
raise ValueError(

Check warning on line 85 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L85

Added line #L85 was not covered by tests
"The length of z must equal the 1th dimension of speed_multipliers "
)

# If wind_directions is note None, check that it is valid then save it
if wind_directions is not None:
if not isinstance(wind_directions, (list, np.ndarray)):
Expand Down Expand Up @@ -121,6 +143,23 @@ def __init__(
"should be unique."
)

def __str__(self) -> str:
"""
Return a string representation of the HeterogeneousMap.
Returns:
str: A string representation of the HeterogeneousMap.
"""
if self.z is None:
num_dim = 2

Check warning on line 153 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L152-L153

Added lines #L152 - L153 were not covered by tests
else:
num_dim = 3

Check warning on line 155 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L155

Added line #L155 was not covered by tests

return (

Check warning on line 157 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L157

Added line #L157 was not covered by tests
f"HeterogeneousMap with {num_dim} dimensions\n"
f"Speeds-up defined for {len(self.x)} points and\n"
f"{self.speed_multipliers.shape[0]} wind conditions"
)

def get_heterogeneous_inflow_config(
self,
wind_directions: NDArrayFloat | list[float],
Expand Down Expand Up @@ -195,12 +234,49 @@ def get_heterogeneous_inflow_config(
self.speed_multipliers, len(wind_directions), axis=0
)

# Return heterogeneous_inflow_config
return {
"x": self.x,
"y": self.y,
"speed_multipliers": speed_multipliers_by_findex,
}
# Return heterogeneous_inflow_config with only x and y is z is not defined
if self.z is None:
return {
"x": self.x,
"y": self.y,
"speed_multipliers": speed_multipliers_by_findex,
}
else:
return {
"x": self.x,
"y": self.y,
"z": self.z,
"speed_multipliers": speed_multipliers_by_findex,
}

def get_heterogeneous_map_2d(self, z: float):
"""
Return a HeterogeneousMap with only x and y coordinates and a constant z value.
Do this by selecting from x, y and speed_multipliers where z is nearest to the given value.
"""
if self.z is None:
raise ValueError("No z values defined in the HeterogeneousMap")

Check warning on line 258 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L258

Added line #L258 was not covered by tests

# Find the value in self.z that is closest to the given z value
closest_z_index = np.argmin(np.abs(self.z - z))

# Get the indices of all the values in self.z that are equal to the closest value
closest_z_indices = np.where(self.z == self.z[closest_z_index])[0]

# Get versions of x, y and speed_multipliers that include only the closest z values
# by selecting the indices in closest_z_indices
x = self.x[closest_z_indices]
y = self.y[closest_z_indices]
speed_multipliers = self.speed_multipliers[:, closest_z_indices]

# Return a new HeterogeneousMap with the new x, y and speed_multipliers
return HeterogeneousMap(
x=x,
y=y,
speed_multipliers=speed_multipliers,
wind_directions=self.wind_directions,
wind_speeds=self.wind_speeds,
)

@staticmethod
def plot_heterogeneous_boundary(x, y, ax=None):
Expand Down Expand Up @@ -277,6 +353,7 @@ def plot_single_speed_multiplier(
self,
wind_direction: float,
wind_speed: float,
z: float = None,
ax: plt.Axes = None,
vmin: float = None,
vmax: float = None,
Expand All @@ -290,6 +367,9 @@ def plot_single_speed_multiplier(
Args:
wind_direction (float): The wind direction for which to plot the speed multipliers.
wind_speed (float): The wind speed for which to plot the speed multipliers.
z (float, optional): The z-coordinate for which to plot the speed multipliers.
If None, the z-coordinate is not used. Only for when z is defined in the
HeterogeneousMap.
ax (matplotlib.axes.Axes, optional): The axes on which to plot the speed multipliers.
If None, a new figure and axes will be created.
vmin (float, optional): The minimum value for the colorbar. Default is the minimum
Expand All @@ -314,21 +394,39 @@ def plot_single_speed_multiplier(
if not isinstance(wind_speed, float):
raise TypeError("wind_speed must be a float")

Check warning on line 395 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L392-L395

Added lines #L392 - L395 were not covered by tests

# Get the speed multipliers for the given wind direction and wind speed
speed_multiplier_row = self.get_heterogeneous_inflow_config(
np.array([wind_direction]), np.array([wind_speed])
)["speed_multipliers"][0]
# If self.z is None, then z should be None
if self.z is None and z is not None:
raise ValueError("No z values defined in the HeterogeneousMap")

Check warning on line 399 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L398-L399

Added lines #L398 - L399 were not covered by tests

# If self.z is defined and has more than one unique value, then z should be defined
if self.z is not None and len(np.unique(self.z)) > 1 and z is None:
raise ValueError(

Check warning on line 403 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L402-L403

Added lines #L402 - L403 were not covered by tests
"Multiple z values defined in the HeterogeneousMap. z must be provided"
)

# Get the 2d version at height z if z is defined and get the speed multiplier from there
# as well as x and y
if z is not None:
hm_2d = self.get_heterogeneous_map_2d(z)
x = hm_2d.x
y = hm_2d.y
speed_multiplier_row = hm_2d.get_heterogeneous_inflow_config(

Check warning on line 413 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L409-L413

Added lines #L409 - L413 were not covered by tests
np.array([wind_direction]), np.array([wind_speed])
)["speed_multipliers"][0]
else:
x = self.x
y = self.y

Check warning on line 418 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L417-L418

Added lines #L417 - L418 were not covered by tests
# Get the speed multipliers for the given wind direction and wind speed
speed_multiplier_row = self.get_heterogeneous_inflow_config(

Check warning on line 420 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L420

Added line #L420 was not covered by tests
np.array([wind_direction]), np.array([wind_speed])
)["speed_multipliers"][0]

# If not provided create the axis
if ax is None:
fig, ax = plt.subplots()

Check warning on line 426 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L425-L426

Added lines #L425 - L426 were not covered by tests
else:
fig = ax.get_figure()

Check warning on line 428 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L428

Added line #L428 was not covered by tests

# Get the x and y coordinates
x = self.x
y = self.y

# Get some boundary info
min_x = np.min(x)
max_x = np.max(x)
Expand All @@ -351,7 +449,7 @@ def plot_single_speed_multiplier(
y_plot = y_plot.flatten()

Check warning on line 449 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L448-L449

Added lines #L448 - L449 were not covered by tests

try:
lin_interpolant = FlowField.interpolate_multiplier_xy(x,y,speed_multiplier_row)
lin_interpolant = FlowField.interpolate_multiplier_xy(x, y, speed_multiplier_row)

Check warning on line 452 in floris/heterogeneous_map.py

View check run for this annotation

Codecov / codecov/patch

floris/heterogeneous_map.py#L451-L452

Added lines #L451 - L452 were not covered by tests

lin_values = lin_interpolant(x, y)
except scipy.spatial._qhull.QhullError:
Expand Down
Loading

0 comments on commit 97946f9

Please sign in to comment.