NatLabRockies / NatLabRockies/H2Integrate

Wind speed estimate at hub-height using shear

Open
#444 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
26
Forks
44
Avg merge
3d 22h
Merged PRs (30d)
16

Description

Wind speed estimate at hub-height using shear

Add function for estimating wind resource at the turbine hub-height using shear. This should be included as a new option that is specific to wind speed (but similar to resource_data_averaging_method) in the FlorisWindPlantPerformanceConfig.

Proposed solution

Theres a few possible options/use-cases:

  1. wind speed only available at one resource height, user must provide wind shear value (psi)
ws_est = uref*(hub_height/zref)**psi
  1. wind speed available at multiple heights, use the resource heights nearest to the hub-height to estimate shear, then using this shear value, estimate the wind speed at the hub-height
def estimate_wind_speed(u0, z0, uref, zref, hub_height):
    psi = np.log(z0/zref)/np.log(u0/uref)
    ws_est = uref*(hub_height/zref)**psi
    return ws_est
  1. wind speed available at multiple heights, use the resource heights nearest to the hub-height to estimate curve coefficients for wind speed from hub-height and use this to estimate wind speed at the hub-height. This can be done in two different ways:
    • one curve-fit for the entire year
    • one curve fit per time-step, although this may require using more than just the bounding heights.
def height_to_winspeed_func(ur_zr_z, psi):
    ur, zr, z = ur_zr_z
    u = ur * (z / zr) ** psi
    return u

For the entire year:

def simple_estimate_wind_speed_with_curve_fit(
    wind_resource_data: dict,
    bounding_resource_heights: tuple[int] | list[int],
    hub_height: float | int,
    ):
    ws_heights = np.array(bounding_resource_heights)
    n_timesteps = len(wind_resource_data[f"wind_speed_{int(ws_heights[0])}m"])
    # calc closest height
    ub_diff = np.abs(np.max(ws_heights) - hub_height)
    lb_diff = np.abs(np.min(ws_heights) - hub_height)

    if ub_diff >= lb_diff:
        # lower-bound is closer, use lower bound as reference and upper bound as input
        z_ref = np.min(ws_heights) * np.ones(n_timesteps)
        ws_ref = wind_resource_data[f"wind_speed_{int(np.min(ws_heights))}m"]
        z = np.max(ws_heights) * np.ones(n_timesteps)
        ws = wind_resource_data[f"wind_speed_{int(np.max(ws_heights))}m"]

    else:
        # upper bound is closer, use upper bound as reference and lower bound as input
        z_ref = np.max(ws_heights) * np.ones(n_timesteps)
        ws_ref = wind_resource_data[f"wind_speed_{int(np.max(ws_heights))}m"]
        z = np.min(ws_heights) * np.ones(n_timesteps)
        ws = wind_resource_data[f"wind_speed_{int(np.min(ws_heights))}m"]

    curve_coeff, curve_cov = scipy.optimize.curve_fit(
        height_to_winspeed_func,
        (ws_ref, z_ref, z),
        ws,
        p0=(1.0),
    )
    ws_at_hubheight = height_to_winspeed_func(
        (ws_ref, z_ref, hub_height * np.ones(n_timesteps)), *curve_coeff
    )
    return ws_at_hubheight

Estimated per timestep, although this code does not work at the moment:

def estimate_wind_speed_with_curve_fit(
    wind_resource_data: dict,
    bounding_resource_heights: tuple[int] | list[int],
    hub_height: float | int,
):
    ws_dict = {k: v for k, v in wind_resource_data.items() if "wind_speed" in k}
    ws_heights = np.array(bounding_resource_heights)
    n_timesteps = len(ws_dict[f"wind_speed_{int(ws_heights[0])}m"])

    # calc closest height
    ub_diff = np.abs(np.max(ws_heights) - hub_height)
    lb_diff = np.abs(np.min(ws_heights) - hub_height)

    if ub_diff >= lb_diff:
        # lower-bound is closer, use lower bound as reference and upper bound as input
        z_ref = np.min(ws_heights) * np.ones(n_timesteps)
        ws_ref = ws_dict[f"wind_speed_{int(np.min(ws_heights))}m"]
        z = np.max(ws_heights) * np.ones(n_timesteps)
        ws = ws_dict[f"wind_speed_{int(np.max(ws_heights))}m"]

    else:
        # upper bound is closer, use upper bound as reference and lower bound as input
        z_ref = np.max(ws_heights) * np.ones(n_timesteps)
        ws_ref = ws_dict[f"wind_speed_{int(np.max(ws_heights))}m"]
        z = np.min(ws_heights) * np.ones(n_timesteps)
        ws = ws_dict[f"wind_speed_{int(np.min(ws_heights))}m"]
    
ws_at_hubheight = np.zeros(n_timesteps)
    for i in range(n_timesteps):
        # error is thrown here for some reason
        curve_coeff, curve_cov = scipy.optimize.curve_fit(
            height_to_winspeed_func,
            (np.array(ws_ref[i]), np.array(z_ref[i]), np.array(z[i])),
            np.array(ws[i]),
            p0=(1.0),
        )
        ws_at_hubheight[i] = height_to_winspeed_func(
            (ws_ref[i], z_ref[i], hub_height), *curve_coeff
        )

Alternatives considered

Additional context

Brought up with the introduction of h2integrate/converters/wind/tools/resource_tools.py in PR #372

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with h2integrate/converters/wind/tools/resource_tools.py and the FlorisWindPlantPerformanceConfig introduced around PR #372. Compare the proposed single-height, annual curve-fit, and per-timestep approaches and determine the intended new wind-speed option; done means the selected approach estimates hub-height wind speed from shear and is integrated into that configuration.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, python
Domain
backend, data
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.