Skip to content
Snippets Groups Projects
example_2_wake_comparison.py 5.13 KiB
Newer Older
Jennifer Rinker's avatar
Jennifer Rinker committed
"""Example: optimization with different wake models

This example uses a dummy cost function to optimize a simple wind turbine
layout that is subject to constraints. The optimization pushes the wind turbine
locations to specified locations in the farm.
"""
import os
import warnings

from matplotlib.patches import Polygon
import matplotlib.pyplot as plt
import numpy as np

from topfarm.plotting import PlotComp
from topfarm import TopFarm
from topfarm.cost_models.utils.wind_resource import WindResource
from topfarm.cost_models.cost_model_wrappers import AEPCostModelComponent
from topfarm.cost_models.fused_wake_wrappers import FusedWakeGCLWakeModel, \
    FusedWakeNOJWakeModel
from topfarm.cost_models.utils.aep_calculator import AEPCalculator


# ------------------------ INPUTS ------------------------

# paths to input files
test_files_dir = os.path.join(os.path.dirname(__file__), '..', 'topfarm',
                              'tests', 'test_files')  # file locations
wf_path = os.path.join(test_files_dir, 'wind_farms',
                       '3tb.yml')  # path to wind farm
f = [3.597152, 3.948682, 5.167395, 7.000154, 8.364547, 6.43485, 8.643194,
     11.77051, 15.15757, 14.73792, 10.01205, 5.165975]  # horns rev
a = [9.176929,  9.782334, 9.531809, 9.909545, 10.04269, 9.593921, 9.584007,
     10.51499, 11.39895, 11.68746, 11.63732, 10.08803]  # horns rev
k = [2.392578, 2.447266, 2.412109, 2.591797, 2.755859, 2.595703, 2.583984,
     2.548828, 2.470703, 2.607422, 2.626953, 2.326172]  # horns rev
rot_diam = 80.0  # rotor diameter [m]
bnd_size = 2 * rot_diam + 10  # boundary size
init_pos = np.array([(0, 2 * rot_diam), (0, 0),
                     (0, -2 * rot_diam)])  # initial turbine positions
boundary = [(-bnd_size, bnd_size), (bnd_size, bnd_size),
            (bnd_size, -bnd_size), (-bnd_size, -bnd_size),
            (-bnd_size, bnd_size)]  # wind farm boundary
min_spacing = 2.0 * rot_diam  # minimum spacing between turbines [m]

# ------------------------ DEFINE WIND RESOURCE ------------------------

wind_res = WindResource(f, a, k, np.zeros_like(k))

# ------------------------ OPTIMIZATION ------------------------

warnings.filterwarnings('ignore')  # temporarily disable fusedwake warnings

# GCL: define the wake model, aep calculator, and optimization problem
wake_mod_gcl = FusedWakeGCLWakeModel(wf_path)
aep_calc_gcl = AEPCalculator(wind_res, wake_mod_gcl)
tf_gcl = TopFarm(init_pos, aep_calc_gcl.get_TopFarm_cost_component(),
                 min_spacing, boundary=boundary)

# NOJ: define the wake model, aep calculator, and optimization problem
wake_mod_noj = FusedWakeNOJWakeModel(wf_path)
aep_calc_noj = AEPCalculator(wind_res, wake_mod_noj)
tf_noj = TopFarm(init_pos, aep_calc_noj.get_TopFarm_cost_component(),
                 min_spacing, boundary=boundary)

# run the optimization
cost_gcl, state_gcl, recorder_gcl = tf_gcl.optimize()
cost_noj, state_noj, recorder_noj = tf_noj.optimize()

# ------------------------ POST-PROCESS ------------------------

# get the optimized locations
opt_gcl = tf_gcl.turbine_positions
opt_noj = tf_noj.turbine_positions

# create the array of costs for easier printing
costs = np.diag([cost_gcl, cost_noj])
costs[0, 1] = TopFarm(opt_gcl, aep_calc_noj.get_TopFarm_cost_component(),
                      min_spacing,
                      boundary=boundary).evaluate()[0]  # noj cost, gcl locs
costs[1, 0] = TopFarm(opt_noj, aep_calc_gcl.get_TopFarm_cost_component(),
                      min_spacing,
                      boundary=boundary).evaluate()[0]  # gcl cost, noj locs

warnings.filterwarnings('default')  # re-enable warnings warnings

# ------------------------ PRINT STATS ------------------------

aep_diffs = 200 * (costs[:, 0] - costs[:, 1]) / (costs[:, 0] + costs[:, 1])
loc_diffs = 200 * (costs[0, :] - costs[1, :]) / (costs[0, :] + costs[1, :])

print('\nComparison of cost models vs. optimized locations:')
print('\nCost    |    GCL_aep      NOJ_aep')
print('---------------------------------')
print(f'GCL_loc |{costs[0,0]:11.2f} {costs[0,1]:11.2f}' +
      f'   ({aep_diffs[0]:.2f}%)')
print(f'NOJ_loc |{costs[1,0]:11.2f} {costs[1,1]:11.2f}' +
      f'   ({aep_diffs[1]:.2f}%)')
print(f'             ({loc_diffs[0]:.2f}%)     ({loc_diffs[1]:.2f}%)')

# ------------------------ PLOT (if possible) ------------------------

try:

    # initialize the figure and axes
    fig = plt.figure(1, figsize=(7, 5))
    plt.clf()
    ax = plt.axes()

    # plot the boundary and desired locations
    ax.add_patch(Polygon(boundary, fill=False,
                         label='Boundary'))  # boundary
    ax.plot(init_pos[:, 0], init_pos[:, 1], 'xk',
            label='Initial')
    ax.plot(opt_gcl[:, 0], opt_gcl[:, 1], 'o',
            label='GCL')
    ax.plot(opt_noj[:, 0], opt_noj[:, 1], '^',
            label='NOJ')

    # make a few adjustments to the plot
    ax.autoscale_view()  # autoscale the boundary
    plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
               ncol=4, mode='expand', borderaxespad=0.)  # add a legend
    plt.tight_layout()  # zoom the plot in
    plt.axis('off')  # remove the axis

    # save the png
    fig.savefig(os.path.basename(__file__).replace('.py', '.png'))

except RuntimeError:
    pass