Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX] Circular upsampling across wind directions #943

Merged
merged 20 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/003_wind_data_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
# bins for which frequency is zero are not simulated. This can be changed by setting the
# compute_zero_freq_occurrence parameter to True.
wind_directions = np.array([200.0, 300.0])
wind_speeds = np.array([5.0, 1.00])
wind_speeds = np.array([5.0, 10.0])
freq_table = np.array([[0.5, 0], [0.5, 0]])
wind_rose = WindRose(
wind_directions=wind_directions, wind_speeds=wind_speeds, ti_table=0.06, freq_table=freq_table
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@

# Setup 2 wind directions (due east and due west)
# and 1 wind speed with uniform probability
wind_directions = np.array([270.0, 90.0])
wind_directions = np.array([90.0, 270.0])
n_wds = len(wind_directions)
wind_speeds = [8.0] * np.ones_like(wind_directions)
turbulence_intensities = 0.06 * np.ones_like(wind_directions)
wind_speeds = np.array([8.0])

# Shape frequency distribution to match number of wind directions and wind speeds
freq_table = np.ones((len(wind_directions), len(wind_speeds)))
freq_table = freq_table / freq_table.sum()
Expand Down Expand Up @@ -106,7 +106,7 @@
fig = plt.gcf()
sm = ax.tricontourf(x_locs, y_locs, speed_multipliers[0], cmap="coolwarm")
fig.colorbar(sm, ax=ax, label="Speed multiplier")
ax.legend(["Initial layout", "Optimized layout", "Optimization boundary"])
ax.legend(["_Optimization boundary", "Initial layout", "Optimized layout" ])
ax.set_title("Geometric yaw disabled")


Expand Down Expand Up @@ -144,7 +144,7 @@
fig = plt.gcf()
sm = ax.tricontourf(x_locs, y_locs, speed_multipliers[0], cmap="coolwarm")
fig.colorbar(sm, ax=ax, label="Speed multiplier")
ax.legend(["Initial layout", "Optimized layout", "Optimization boundary"])
ax.legend(["_Optimization boundary", "Initial layout", "Optimized layout"])
ax.set_title("Geometric yaw enabled")

print(
Expand Down
101 changes: 101 additions & 0 deletions floris/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,107 @@ def wrap_360(x):

return x % 360.0

def check_and_identify_step_size(wind_directions):
"""
This function identifies the step size in a series of wind directions. The function will
return the step size if the wind directions are evenly spaced, otherwise it will raise an
error.

Args:
wind_directions (np.ndarray): Array of wind directions.

Returns:
float: The step size of the wind directions.
"""

if len(wind_directions) < 2:
raise ValueError("Array must contain at least 2 elements")

# First compute the steps between each wind direction
steps = np.diff(wind_directions)

# Confirm that the steps are all positive
if not np.all(steps > 0):
raise ValueError("wind_directions must be monotonically increasing")

# Check the step from the last to the first element
last_step = wind_directions[0] - wind_directions[-1] + 360

# If len(window_directions) == 2, then return whichever step is smaller
if len(wind_directions) == 2:
return min(steps[0], last_step)

# If len(window_directions) == 3 make some checks
elif len(wind_directions) == 3:
if np.all(steps == steps[0]):
return steps[0]
elif steps[0] == last_step:
return steps[0]
elif steps[1] == last_step:
return steps[1]
else:
raise ValueError("wind_directions must be evenly spaced")

else:
if np.all(steps == steps[0]):
return steps[0]

# If all but one of the steps are the same
values, counts = np.unique(steps, return_counts=True)

# Check for the case where there are more than two different step sizes
if len(values) > 2:
raise ValueError("wind_directions must be evenly spaced")

# In the case there are only two step sizes, ensure that one only happens once
if np.min(counts) > 1:
raise ValueError("wind_directions must be evenly spaced")

# If the last step equals the most common step, return the most common step
if last_step == values[np.argmax(counts)]:
return values[np.argmax(counts)]

raise ValueError("wind_directions must be evenly spaced")

def make_wind_directions_adjacent(wind_directions: NDArrayFloat) -> NDArrayFloat:
"""
This function reorders the wind directions so that they are adjacent. The function will
return the reordered wind directions if the wind directions are not adjacent, otherwise it
will return the input wind directions

Args:
wind_directions (NDArrayFloat): Array of wind directions.

Returns:
NDArrayFloat: The reordered wind directions to be adjacent.
"""

# Check the step size of the wind directions
step_size = check_and_identify_step_size(wind_directions)

# Get a list of steps
steps = np.diff(wind_directions)

# There will be at most one step with a size larger than the step size
# If there is one, find it
if np.any(steps > step_size):
idx = np.argmax(steps)

# Now change wind_directions such that for each direction after that index
# subtract 360 and move that block to the front
wind_directions = np.concatenate(
(wind_directions[idx+1:] - 360, wind_directions[:idx+1])
)

# Return the wind directions and indices to go from the original to the new
sort_indices = np.array(list(range(idx+1,len(wind_directions))) + list(range(idx+1)))

return wind_directions, sort_indices

else:

return wind_directions, np.arange(len(wind_directions))


def wind_delta(wind_directions: NDArrayFloat | float):
"""
Expand Down
Loading
Loading