NREL / flasc

A rich floris-driven suite for SCADA analysis
https://nrel.github.io/flasc/
BSD 3-Clause "New" or "Revised" License
30 stars 18 forks source link

Feature: Identify impacting turbines geometrically and reduce FLORIS accordingly #158

Closed Bartdoekemeijer closed 7 months ago

Bartdoekemeijer commented 7 months ago

This PR is ready to be merged, but may require examples.

Feature or improvement description This function, through very simplified geometry (linear wake expansion, not modeling deficit), identifies which turbines are impacting a set of "turbines of interest". This can be helpful when you are trying to identify which neighboring wind farms affect your farm of interest for a particular wind direction. This function runs in a matter of seconds, vs. the current function which runs FLORIS simulations and can take hours for large farms. At times, this reduction can cut down the size of your FLORIS model by more than 50% and thereby give you massive speed-ups.

Related issue, if one exists N/A

Impacted areas of the software FLORIS tools.

Additional supporting information N/A

Test results, if applicable This still requires a simple example.

paulf81 commented 7 months ago

This looks really nice @Bartdoekemeijer, thank you! @misi9170 and I are pretty deep in FLORIS v4 right now, but soon hope to come back to FLASC v2 work. This is a pretty small request though, so I'm tempted to approve it quickly once we have a small example or test,

Bartdoekemeijer commented 7 months ago

Great @paulf81! Here's a quick example:

import matplotlib.pyplot as plt
import numpy as np

from floris.tools import FlorisInterface
from flasc.floris_tools import reduce_floris_object, get_all_impacting_turbines_geometrical
from flasc.visualization import plot_floris_layout, plot_layout_only

# Load a large FLORIS object
fi = FlorisInterface("inputs/gch.yaml")
D = 126.0
X, Y = np.meshgrid(7.0 * D * np.arange(20), 5.0 * D * np.arange(20))
fi.reinitialize(layout_x=X.flatten(), layout_y=Y.flatten())

# Specify which turbines are of interest
turbine_weights = np.zeros(len(X.flatten()), dtype=float)
turbine_weights[np.hstack([a + range(10) for a in np.arange(50, 231, 20)])] = 1.0

# Get all impacting turbines for each wind direction using simple geometry rules
df_impacting = get_all_impacting_turbines_geometrical(
    fi=fi,
    turbine_weights=turbine_weights,
    wd_array=np.arange(0.0, 360.0, 30.0)
)

# Produce plots showcasing which turbines are estimated to be impacting
for ii in range(df_impacting.shape[0]):
    wd = df_impacting.loc[ii, "wd"]

    fig, ax = plt.subplots()
    ax.plot(fi.layout_x, fi.layout_y, "o", color="lightgray", label="All turbines")

    ids = df_impacting.loc[ii, "impacting_turbines"]
    no_turbines_total = len(fi.layout_x)
    no_turbines_reduced = len(ids)
    ax.plot(fi.layout_x[ids], fi.layout_y[ids], "o", color="black", label="Impacting turbines")

    ids = np.where(turbine_weights > 0.001)[0]
    ax.plot(fi.layout_x[ids], fi.layout_y[ids], "o", color="red", label="Turbines of interest")

    ax.set_xlabel("X location (m)")
    ax.set_ylabel("Y location (m)")
    ax.axis("equal")
    ax.grid(True)
    ax.legend()
    percentage = 100.0 * no_turbines_reduced / no_turbines_total
    ax.set_title(f"Wind direction: {wd:.1f} deg. Turbines modelled: {no_turbines_reduced:d}/{no_turbines_total} ({percentage:.1f}%).")

    # Make a statement on number of wake-steered turbines vs. total farm size
    print(f"wd={wd:.1f} deg. Reduced from {no_turbines_total:d} to {no_turbines_reduced} ({percentage:.1f}%).")

plt.show()

This results in:

wd=0.0 deg. Reduced from 400 to 206 (51.5%).
wd=30.0 deg. Reduced from 400 to 170 (42.5%).
wd=60.0 deg. Reduced from 400 to 153 (38.2%).
wd=90.0 deg. Reduced from 400 to 129 (32.2%).
wd=120.0 deg. Reduced from 400 to 117 (29.2%).
wd=150.0 deg. Reduced from 400 to 118 (29.5%).
wd=180.0 deg. Reduced from 400 to 130 (32.5%).
wd=210.0 deg. Reduced from 400 to 164 (41.0%).
wd=240.0 deg. Reduced from 400 to 215 (53.8%).
wd=270.0 deg. Reduced from 400 to 307 (76.8%).
wd=300.0 deg. Reduced from 400 to 311 (77.8%).
wd=330.0 deg. Reduced from 400 to 275 (68.8%).

figure_0

figure_1

figure_2

figure_5

figure_9

paulf81 commented 7 months ago

hi @Bartdoekemeijer I hope it's ok, I pushed up a few commits, one including the above example, and then I ruff formatted floris_tools.py and removed some unused variables, looks good to go on my end!

Bartdoekemeijer commented 7 months ago

Perfect, looks good to me! Please go ahead and merge!

paulf81 commented 7 months ago

Merging now, thank you @Bartdoekemeijer !