Wind Farm AEP Optimization in SciPy – Horns Rev 1 Site

  • The objective of this post is to implement the wind farm AEP optimization algorithm in SciPy. Results are to be compared with the PyWake AEP calculator for the Horns Rev 1 site.
  • Horns Rev 1 is known to be a game-changing step towards achieving Vattenfall’s ambitions of multiplying wind power capacity within the next 10–15 years. The wind farm is also consistent with the Danish government’s target of increasing electricity generation by offshore wind farms.

Importing Libraries

Let’s set the working directory YOURPATH

import os
os.chdir('YOURPATH')    # Set working directory
os. getcwd()

and import the following libraries/modules

import py_wake
import scipy
import time

from py_wake.examples.data.hornsrev1 import V80 
from py_wake.examples.data.hornsrev1 import Hornsrev1Site # We work with the Horns Rev 1 site, which comes already set up with PyWake.
from py_wake import BastankhahGaussian
from py_wake.examples.data.hornsrev1 import wt_x, wt_y
from scipy.optimize import minimize
import optuna
import matplotlib.pyplot as plt
import numpy as np

Hornsrev1 Layout

After importing the properties of Hornsrev1, which are already stored in PyWake, we instantiate them

site = Hornsrev1Site()
wt = V80()

Let’s look at the Wind Rose Plot for Direction

site.plot_wd_distribution(n_wd=12);
Wind Rose Plot for Direction

Let’s plot the V80 wind farm layout

plt.figure()
wt.plot(wt_x, wt_y)
plt.xlabel('x [m]')
plt.ylabel('y [m]')
The V80 wind farm layout

SciPy Optimization

Let’s introduce the following functions

def funC(x, y, c):
    """
    Turns on/off the use of wind turbine depending on the value of c.
    scipy generates c real values in the range [0, 1] as specified by the bounds including 0.2 etc.
    If c is 0.5 or more turbine will be used otherwise turbine will not be used.
    """
    x_selected = x[c >= 0.5]
    y_selected = y[c >= 0.5]
    
    return (x_selected, y_selected)


def wt_simulation(c):
    """
    This is our objective function. It will return the aep=annual energy production in GWh.
    We will maximize aep.
    """
    site = Hornsrev1Site()
    x, y = site.initial_position.T
    windTurbines = V80()
    
    wf_model = BastankhahGaussian(site, windTurbines)
    x_new, y_new = funC(x, y, c)

    # run wind farm simulation
    sim_res = wf_model(
        x_new, y_new, # wind turbine positions
        h=None, # wind turbine heights (defaults to the heights defined in windTurbines)
        type=0, # Wind turbine types
        wd=None, # Wind direction (defaults to site.default_wd (0,1,...,360 if not overriden))
        ws=None, # Wind speed (defaults to site.default_ws (3,4,...,25m/s if not overriden))
    )
    
    aep_output = sim_res.aep().sum()  # we maximize aep
    
    return -float(aep_output)  # negate because of scipy minimize


def solve():
    t0 = time.perf_counter()
    
    wt = 80  # for V80

    x0 = np.ones(wt)  # initial value
    bounds = [(0, 1) for _ in range(wt)]

    res = minimize(wt_simulation, x0=x0, bounds=bounds)
    
    print(f'success status: {res.success}')
    print(f'aep: {-res.fun}')  # negate to get the true maximum aep
    print(f'c values: {res.x}\n')

    print(f'elapse: {round(time.perf_counter() - t0)}s') 

and run the simulation

# start
solve()

Output:

success status: True
aep: 682.0407252944838
c values: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1.]

elapse: 122s
  • The improved PyWake AEP calculation by modifying turbine locations gives 682.3606786316058 (GWh), an increase of 0.024718 % with respect to the original PyWake AEP.

Optuna HPO

Let’s try to improve our results by invoking Optuna HPO by considering c as hyperparameter. Let’s define the following functions

def funC(x, y, c):
    """
    Turns on/off the use of wind turbine depending on the value of c.
    optuna generates c integer values in the range [0, 1] as specified by the bounds.
    If c is 1 turbine will be run otherwise turbine will not be run.
    """
    c = np.array(c)
    
    x_selected = x[c == 1]
    y_selected = y[c == 1]
    
    return (x_selected, y_selected)


def objective(trial):
    """
    This is our objective function. It will return the aep=annual energy production in GWh.
    We will maximize aep.
    """
    site = Hornsrev1Site()
    x, y = site.initial_position.T
    windTurbines = V80()
    wt = 80  # for v80
    
    # We ask values of c from optuna.
    c = []
    for i in range(wt):
        varname = f'c{i}'
        minv, maxv, stepv = 0, 1, 1
        c.append(trial.suggest_int(varname, minv, maxv, step=stepv))
    
    wf_model = BastankhahGaussian(site, windTurbines)
    x_new, y_new = funC(x, y, c)

    # run wind farm simulation
    sim_res = wf_model(
        x_new, y_new, # wind turbine positions
        h=None, # wind turbine heights (defaults to the heights defined in windTurbines)
        type=0, # Wind turbine types
        wd=None, # Wind direction (defaults to site.default_wd (0,1,...,360 if not overriden))
        ws=None, # Wind speed (defaults to site.default_ws (3,4,...,25m/s if not overriden))
    )
    
    aep_output = sim_res.aep().sum()
    
    return float(aep_output)  # give feedback to optuna of how the c we asks has performed


def optuna_hpo():
    t0 = time.perf_counter()
    
    num_trials = 300
    sampler = optuna.integration.SkoptSampler()
    
    study = optuna.create_study(sampler=sampler, direction="maximize")
    study.optimize(objective, n_trials=num_trials)
    
    print(f"Best params: {study.best_params}")
    print(f"Best value: {study.best_value}\n")
    
    print(f'elapse: {round(time.perf_counter() - t0)}s')  
    

and run HPO

# start
optuna_hpo()

Output:

Best params: {'c0': 1, 'c1': 1, 'c2': 1, 'c3': 1, 'c4': 1, 'c5': 1, 'c6': 1, 'c7': 1, 'c8': 1, 'c9': 1, 'c10': 1, 'c11': 1, 'c12': 1, 'c13': 1, 'c14': 1, 'c15': 1, 'c16': 1, 'c17': 1, 'c18': 1, 'c19': 1, 'c20': 1, 'c21': 1, 'c22': 1, 'c23': 1, 'c24': 1, 'c25': 1, 'c26': 1, 'c27': 1, 'c28': 1, 'c29': 1, 'c30': 1, 'c31': 1, 'c32': 1, 'c33': 1, 'c34': 1, 'c35': 1, 'c36': 1, 'c37': 1, 'c38': 1, 'c39': 1, 'c40': 1, 'c41': 1, 'c42': 1, 'c43': 1, 'c44': 1, 'c45': 1, 'c46': 1, 'c47': 1, 'c48': 1, 'c49': 1, 'c50': 1, 'c51': 1, 'c52': 1, 'c53': 1, 'c54': 1, 'c55': 1, 'c56': 1, 'c57': 1, 'c58': 1, 'c59': 1, 'c60': 1, 'c61': 1, 'c62': 1, 'c63': 1, 'c64': 1, 'c65': 1, 'c66': 1, 'c67': 1, 'c68': 1, 'c69': 1, 'c70': 1, 'c71': 1, 'c72': 1, 'c73': 1, 'c74': 1, 'c75': 1, 'c76': 1, 'c77': 1, 'c78': 1, 'c79': 1}
Best value: 682.0407252944838

elapse: 3173s

It found the maximum AEP of 682.0407252944838 as early as trial 109 out of 299 trials. This confirms that what SciPy saw is also seen by other optimizer such as Optuna.

Summary

  • In this study, we have applied the SciPy solver to find the maximum AEP for the offshore Horns Rev 1 site operated by Vattenfall.
  • Results confirm that what SciPy saw is also seen by other optimizers such as Optuna and PyWake (courtesy of DTU) for both original and modified layouts of wind turbines.
  • This project supports the Danish government’s target of increasing electricity generation by offshore wind farms.

References

Explore More


Go back

Your message has been sent

Warning

One-Time
Monthly
Yearly

Make a one-time donation

Make a monthly donation

Make a yearly donation

Choose an amount

€5.00
€15.00
€100.00
€5.00
€15.00
€100.00
€5.00
€15.00
€100.00

Or enter a custom amount


Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly

Discover more from Our Blogs

Subscribe to get the latest posts sent to your email.

Leave a comment

Discover more from Our Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading