Tidal Power Performance Analysis

The following example demonstrates a simple workflow for conducting the power performance analysis of a turbine, given turbine specifications, power data, and Acoustic Doppler Current Profiler (ADCP) water measurements.

In this case, the turbine specifications can be broken down into 1. Shape of the rotor’s swept area 2. Turbine rotor diameter/height and width 3. Turbine hub height (center of swept area)

Additional data needed: - Power data from the current energy converter (CEC) - 2-dimensional water velocity data

In this jupyter notebook, we’ll be covering the following three topics: 1. CEC power-curve 2. Velocity profiles 3. CEC efficiency profile (or power coefficient profile)

Start by importing the necessary tools:

[1]:
import numpy as np
import matplotlib.pyplot as plt

from mhkit.tidal import performance
from mhkit.dolfyn import load
c:\Users\mcve343\Anaconda3\lib\site-packages\xarray\backends\cfgrib_.py:29: UserWarning: Failed to load cfgrib - most likely there is a problem accessing the ecCodes library. Try `import cfgrib` to get the full error message
  warnings.warn(

In this case, we’ll use ADCP data from the ADCP example notebook. I am importing a dataset from the ADCP example notebook. This data retains the original timestamps (1 Hz sampling frequency) and was rotated into the principal coordinate frame (streamwise-cross_stream-up).

[2]:
# Open processed ADCP dataset
ds = load("data/tidal/adcp.principal.a1.20200815.nc")

Next, since we don’t have power data, we’ll invent a mock timeseries based off the cube of water velocity, just to have something to work with.

[3]:
# Streamwise and hub-height water velocity
streamwise_vel = ds["vel"].sel(dir="streamwise")
hub_height_vel = abs(streamwise_vel.isel(range=10))

# Emulate power data
power = hub_height_vel**3 * 1e5
# Emulate cut-in speed by setting power at flow speeds below 0.5 m/s to 0 W
power = power.where(abs(streamwise_vel.mean("range")) > 0.5, 0)

The first step for any of the following calculations is to first split velocity into ebb and flood tide. You’ll need some background information on the site to know which direction is positive and which is negative in the data.

[4]:
ebb = streamwise_vel.where(streamwise_vel > 0)
flood = streamwise_vel.where(streamwise_vel < 0)

With the ebb and flood velocities, we can also divide the power data into that for ebb and flood tides.

[5]:
# Make sure ebb and flood are on same timestamps
power = power.interp(time=streamwise_vel["time"])

power_ebb = power.where(~ebb.mean("range").isnull(), 0)
power_flood = power.where(~flood.mean("range").isnull(), 0)

Power-curve

Now with power and velocity divided into ebb and flood tides, we can calculate the power curve for the CEC in both conditions

[6]:
power_curve_ebb = performance.power_curve(
    power_ebb,
    velocity=ebb,
    hub_height=4.2,
    doppler_cell_size=0.5,
    sampling_frequency=1,
    window_avg_time=600,
    turbine_profile="circular",
    diameter=3,
    height=None,
    width=None,
)
power_curve_flood = performance.power_curve(
    power_flood,
    velocity=flood,
    hub_height=4.2,
    doppler_cell_size=0.5,
    sampling_frequency=1,
    window_avg_time=600,
    turbine_profile="circular",
    diameter=3,
    height=None,
    width=None,
)
[7]:
power_curve_flood
[7]:
U_avg U_avg_power_weighted P_avg P_std P_max P_min
U_bins
(0.0, 0.1] 0.067459 0.000000 0.000000 0.000000 0.000000 0.000000
(0.1, 0.2] 0.115614 0.000000 0.000000 0.000000 0.000000 0.000000
(0.2, 0.3] 0.249676 0.225639 0.000000 0.000000 0.000000 0.000000
(0.3, 0.4] 0.339600 0.315561 0.000000 0.000000 0.000000 0.000000
(0.4, 0.5] 0.459393 0.437249 2890.724986 2660.810022 5551.535008 229.914964
(0.5, 0.6] 0.548507 0.532974 19677.343518 4645.890936 24323.234454 15031.452582
(0.6, 0.7] 0.671449 0.655362 40369.435517 3679.260135 45506.306677 37083.470337
(0.7, 0.8] 0.726189 0.704845 52413.972024 2856.737142 57360.861473 50670.102583
(0.8, 0.9] 0.843958 0.825916 79944.000855 9798.569674 96206.928025 66531.815452
(0.9, 1.0] 0.938701 0.920960 103970.042175 5828.263891 112163.977434 99100.055332
(1.0, 1.1] 1.046607 1.026293 148511.100008 18809.350864 171583.550611 124179.073981
(1.1, 1.2] 1.147348 1.127691 200340.820581 6299.518554 209073.741656 187772.752668

Next we can plot the two power curves. A velocity bin is missing in the ebb tide power curve in this example because the data is so short, there are no samples for that bin.

[8]:
def plot_power_curve(P_curve, ax):
    ax.plot(P_curve["U_avg"], P_curve["P_avg"], "-o", color="C0", label="Avg Power")
    ax.plot(
        P_curve["U_avg"],
        (P_curve["P_avg"] - P_curve["P_std"]),
        "--+",
        color="C1",
        label="Power - 1 Std Dev",
    )
    ax.plot(
        P_curve["U_avg"],
        (P_curve["P_avg"] + P_curve["P_std"]),
        "-+",
        color="C1",
        label="Power + 1 Std Dev",
    )
    ax.plot(P_curve["U_avg"], P_curve["P_min"], "--x", color="C2", label="Min Power")
    ax.plot(P_curve["U_avg"], P_curve["P_max"], "-x", color="C2", label="Max Power")
    ax.set(xlabel="Flow Speed at Hub Height [m/s]", ylabel="Power [W]")
    ax.legend()


fig, ax = plt.subplots(1, 2, figsize=(10, 7))
plot_power_curve(power_curve_ebb, ax[0])
plot_power_curve(power_curve_flood, ax[1])
_images/tidal_performance_example_14_0.png

Velocity Profiles

Various velocity profiles can be created next from the water velocity data, and we can do this again with ebb and flood tide. These functions are following three steps: 1. Reshape the data into bins by time (ensembles) 2. Apply a function to the ensembles to get ensemble statistics (mean, root-mean-square (RMS), or standard devation) 3. Regroup and bin the ensemble statistics by flow speed

These profiles are created using the velocity_profiles method, and a profile is specified using the “function” argument. For the average velocity profiles, we’ll set the function = ‘mean’.

[9]:
avg_profile_ebb = performance.velocity_profiles(
    velocity=ebb,
    hub_height=4.2,
    water_depth=10,
    sampling_frequency=1,
    window_avg_time=600,
    function="mean",
)
avg_profile_flood = performance.velocity_profiles(
    velocity=ebb,
    hub_height=4.2,
    water_depth=10,
    sampling_frequency=1,
    window_avg_time=600,
    function="mean",
)

RMS Tidal Velocity

For RMS velocity profiles, we’ll set the function = ‘rms’.

[10]:
rms_profile_ebb = performance.velocity_profiles(
    velocity=ebb,
    hub_height=4.2,
    water_depth=10,
    sampling_frequency=1,
    window_avg_time=600,
    function="rms",
)
rms_profile_flood = performance.velocity_profiles(
    velocity=ebb,
    hub_height=4.2,
    water_depth=10,
    sampling_frequency=1,
    window_avg_time=600,
    function="rms",
)

Std Dev Tidal Velocity

And to get the standard deviation, we’ll set function = ‘std’.

[11]:
std_profile_ebb = performance.velocity_profiles(
    velocity=ebb,
    hub_height=4.2,
    water_depth=10,
    sampling_frequency=1,
    window_avg_time=600,
    function="std",
)
std_profile_flood = performance.velocity_profiles(
    velocity=ebb,
    hub_height=4.2,
    water_depth=10,
    sampling_frequency=1,
    window_avg_time=600,
    function="std",
)

Finally, we can plot these variables together based on ebb and flood tides. The following code plots the mean and RMS profiles as line plots with “x” and “+” markers, respectively, and shades the area between +/- 1 standard deviation from the mean.

[12]:
def plot_velocity_profiles(avg_profile, rms_profile, std_profile, ax):
    alt = avg_profile.index
    mean = avg_profile.values.T
    rms = rms_profile.values.T
    std = std_profile.values.T

    ax.plot(mean[0], alt, "-x", label=avg_profile.columns[0])
    ax.plot(mean[1], alt, "-x", label=avg_profile.columns[1])
    ax.plot(mean[2], alt, "-x", label=avg_profile.columns[2])

    ax.fill_betweenx(alt, mean[0] - std[0], mean[0] + std[0], facecolor="lightblue")
    ax.fill_betweenx(alt, mean[1] - std[1], mean[1] + std[1], facecolor="moccasin")
    ax.fill_betweenx(alt, mean[2] - std[2], mean[2] + std[2], facecolor="palegreen")

    ax.plot(rms[0], alt, "+", color="C0")
    ax.plot(rms[1], alt, "+", color="C1")
    ax.plot(rms[2], alt, "+", color="C2")
    ax.set(xlabel="Water Velocity [m/s]", ylabel="Altitude [m]", ylim=(0, 10))
    ax.legend()


fig, ax = plt.subplots(1, 2, figsize=(10, 7))
plot_velocity_profiles(avg_profile_ebb, rms_profile_ebb, std_profile_ebb, ax[0])
ax[0].set_title("Ebb Tide")
plot_velocity_profiles(avg_profile_flood, rms_profile_flood, std_profile_flood, ax[1])
ax[1].set_title("Flood Tide")
[12]:
Text(0.5, 1.0, 'Flood Tide')
_images/tidal_performance_example_22_1.png

Current Energy Converter Efficiency

The CEC efficiency, or device power coefficient, can be found using the device_efficiency method.

[13]:
efficiency_ebb = performance.device_efficiency(
    power=power_ebb,
    velocity=ebb,
    water_density=ds["water_density"],
    capture_area=np.pi * 1.5**2,
    hub_height=4.2,
    sampling_frequency=1,
    window_avg_time=600,
)
efficiency_flood = performance.device_efficiency(
    power=power_flood,
    velocity=flood,
    water_density=ds["water_density"],
    capture_area=np.pi * 1.5**2,
    hub_height=4.2,
    sampling_frequency=1,
    window_avg_time=600,
)

And these efficiency curves can be plotted as profiles:

[14]:
def plot_efficiency(efficiency, ax):
    means = efficiency.U_avg.values.T
    eta = efficiency.Efficiency.values.T
    ax.plot(means, eta, "-o")
    ax.set(xlabel="Hub Height Flow Velocity [m/s]", ylabel="Efficiency [%]")


fig, ax = plt.subplots(1, 2, figsize=(7, 6))
plot_efficiency(efficiency_ebb, ax[0])
ax[0].set_title("Ebb Tide")
plot_efficiency(efficiency_flood, ax[1])
ax[1].set_title("Flood Tide")
[14]:
Text(0.5, 1.0, 'Flood Tide')
_images/tidal_performance_example_26_1.png
[ ]: