Add files via upload

This commit is contained in:
Sokratis Anagnostopoulos
2023-03-29 17:14:09 +02:00
committed by GitHub
parent 157fc2c1d8
commit bd4a43535e
19 changed files with 2210 additions and 0 deletions

330
Code/CNNWake/CNN_model.py Normal file
View File

@ -0,0 +1,330 @@
import torch
import torch.nn as nn
import numpy as np
import random
import floris.tools as wfct
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
class Generator(nn.Module):
"""
The class is the Neural Network that generates the flow field around a
wind turbine. The network uses the pytorch framwork and uses fully
connected and transpose convolutional layers.
The methods of this class include the training of the network,
testing of the accuracy and generaton of the training data.
"""
def __init__(self, nr_input_var, nr_filter):
"""
init method that generates the network architecture using pytroch's
ConvTranspose2d and Sequential layers. The number of input varibles
and size of the given network can be changed. The output size will not
change and it set at 163 x 163 pixels.
Args:
nr_input_var (int): Nr. of inputs, usually 3 for u, ti and yaw
nr_filter (int): Nr. filters used in deconv layers, more filters
means that the network will have more parameters
"""
super(Generator, self).__init__()
# linear layer
self.FC_Layer = nn.Sequential(nn.Linear(in_features=nr_input_var,
out_features=9))
# Deconvolutional layer
self.net = nn.Sequential(
self.layer(1, nr_filter * 16, 4, 2, 1),
self.layer(nr_filter * 16, nr_filter * 8, 4, 1, 1),
self.layer(nr_filter * 8, nr_filter * 8, 4, 2, 1),
self.layer(nr_filter * 8, nr_filter * 4, 4, 2, 1),
self.layer(nr_filter * 4, nr_filter * 4, 3, 2, 1),
nn.ConvTranspose2d(nr_filter * 4, 1, kernel_size=3,
stride=3, padding=1),
)
def layer(self, in_filters, out_filters, kernel_size, stride, padding):
"""
One layer of the CNN which consits of ConvTranspose2d,
a batchnorm and LRelu activation function.
Function is used to define one layer of the network
Args:
in_filters (int): Nr. of filters in the previous layer
out_filters (int): Nr. of output filters
kernel_size (int): Size of the ConvTranspose2d layer
stride (int): Stride of the ConvTranspose2d layer
padding (int): Padding used in this layer
Returns:
nn.Sequential: Pytroch Sequential container that defines one layer
"""
# One layer of the network uses:
# Deconvolutional layer, then batch norm and leakyrelu
# activation function
single_layer = nn.Sequential(nn.ConvTranspose2d(in_filters,
out_filters,
kernel_size,
stride,
padding,
bias=False, ),
nn.BatchNorm2d(out_filters),
nn.LeakyReLU(0.2), )
return single_layer
def initialize_weights(self):
"""
Initilize weights using a normal distribution with mean = 0,std2 = 0.02
which has helped training. Loop over all modules, if module is
convolutional layer or batchNorm then initialize weights.
Args:
model (torch model): Neural network model defined using Pytorch
"""
# for ever layer in model
for m in self.modules():
# check if it deconvolutional ot batch nrom layer
if isinstance(m, (nn.Conv2d, nn.BatchNorm2d)):
# initialize weights using a normal distribution
nn.init.normal_(m.weight.data, 0.0, 0.02)
def forward(self, x):
"""
Functions defines a forward pass though the network. Can be used for
a single input or a batch of inputs
Args:
x (torch.tensor): input tensor, to be passed through the network
Returns:
flow_fields (torch.tensor): Output of network
"""
# first the fully connected layer takes in the input, and outputs
# 9 neurons which are reshaped into a 3x3 array
x = self.FC_Layer(x).view(len(x), -1, 3, 3)
# the Conv layers take in the 3x3 array and output a 163x163 array
return self.net(x)
@staticmethod
def create_floris_dataset(
size, image_size, u_range, ti_range, yaw_range,
floris_init_path=".", curl=False):
"""
Function to generate the dataset needed for training using FLORIS.
The flowfield around a turbine is generated for a large range of wind
speeds, turbulent intensities and yaw angles. The 2d array and
correspoding init conditions are saved for training. The data is
generated using a Gaussian wake mode, please see:
https://doi.org/10.1016/j.renene.2014.01.002.
For more information about FLORIS see: https://github.com/NREL/floris.
Function can be used to generated training, validation and test sets.
Args:
size (int): Size of the dataset
image_size (int): Size of the flow field outputs that
are generated, this depends on the
Neural network used, should be 163.
u_range (list): Bound of u values [u_min, u_max] used
ti_range (list): Bound of TI values [TI_min, TI_max] used
yaw_range (list): Bound of yaw angles [yaw_min, yaw_max] used
floris_init_path (str, optional): Path to the FLORIS jason file.
Defaults to ".".
curl (bool, optional): If curl model should be used please set
to True, see this for more information:
https://doi.org/10.5194/wes-4-127-2019.
Defaults to False.
Returns:
y (torch.tensor): Tensor of size (size, image_size, image_size)
which includes all the generated flow fields. The flow fields
are normalised to help training
x (torch.tensor): Tensor of size (size, 1, 3) which includes the
flow conditons of the correspoding flow field in the x tensor.
"""
# sample u, ti and yawn angle from a uniform distribution
u_list = [round(random.uniform(u_range[0], u_range[1]), 2) for
i in range(0, size)]
ti_list = [round(random.uniform(ti_range[0], ti_range[1]), 2) for
i in range(0, size)]
yawn_list = [round(random.uniform(yaw_range[0], yaw_range[1]), 2) for
i in range(0, size)]
# initialize FLORIS model using the jason file
if curl is False:
floris_turbine = wfct.floris_interface.FlorisInterface(
floris_init_path + "/FLORIS_input_gauss.json")
else:
floris_turbine = wfct.floris_interface.FlorisInterface(
floris_init_path + "/FLORIS_input_curl.json")
# initialize empty numpy array to store 2d arrays and
# corresponding u, ti and yawn values
y = np.zeros((size, image_size, image_size))
x = np.zeros((size, 3))
# create train examples
print("generate FLORIS data")
for _ in range(0, size):
if _ % 500 == 0:
print(f"{_}/{size}")
# set wind speed, ti and yawn angle for FLORIS model
floris_turbine.reinitialize_flow_field(
wind_speed=u_list[_],
turbulence_intensity=ti_list[_])
floris_turbine.change_turbine([0], {'yaw_angle': yawn_list[_]})
# calculate the wakefield
floris_turbine.calculate_wake()
# extract horizontal plane at hub height
cut_plane = floris_turbine.get_hor_plane(
height=90,
x_resolution=image_size,
y_resolution=image_size,
x_bounds=[0, 3000],
y_bounds=[-200, 200]).df.u.values.reshape(image_size,
image_size)
# save the wind speed values of the plane at hub height and
# the corresponding turbine stats
y[_] = cut_plane
x[_] = u_list[_], ti_list[_], yawn_list[_]
# turn numpy array into a pytroch tensor
x = torch.tensor(x, dtype=torch.float)
# The wind speeds are normalised by dividing it by 12
# i.e. every value will be between 0-1 which helps training
y = torch.tensor(y, dtype=torch.float)/12
return x, y
def error(self, x_eval, y_eval, device, image_size=163, normalisation=12):
r"""
Calculate the average pixel wise percentage error of the model on
a evaluation set. For error function is:
error = 1/set_size *\sum_{n=0}^{set_size}(1/image_size**2 *
\sum_{i=0}^{image_size**2}(100*abs(FLORIS_{n,i} - GAN_{n,i})/
max(FLORIS_{n,i})))
For a detailed explanation of this function please see the report in
the ACSE9 repo.
"""
error_list = []
# Use model to predict the wakes for the given conditions in x
model_predict = self.forward(x_eval.to(device))
for n in range(0, len(x_eval)):
# Calculate the mean error between CNNwake output and FLORIS
# for a given flow field using the function given above
pixel_error = np.sum(abs(
y_eval.detach().cpu().numpy()[n] -
model_predict.squeeze(1)[n].detach().cpu().numpy()) /
(torch.max(y_eval.detach()[n]).cpu().numpy()))
# divide by number of pixels in array for an mean value
pixel_error /= image_size * image_size
error_list.append(pixel_error * 100)
# return mean error
return np.mean(error_list)
def evaluate_model(self, set_size, u_range, ti_range, yaw_range,
image_size=163, device='cpu', normalisation=12,
florisjason_path="."):
"""
Function to calculate a average pixel wise percentage error
of the model using the error function. This functions generates
a test set and evaluates the model on this unseen data to provide
a test error.
Args:
set_size (int, optional): Nr. of samples to be used for testing.
u_range (list): Bound of u values [u_min, u_max] used
ti_range (list): Bound of TI values [TI_min, TI_max] used
yaw_range (list): Bound of yaw angles [yaw_min, yaw_max] used
image_size (int, optional): Size of the flow field.
Defaults to 163.
device (str): Device to store and run the neural network on,
either cpu or cuda.
normalisation (int, optional): The CNN output is between
0 and 1 due to the normalisation used, therefore it needs to
be renormalised. Defaults to 12.
florisjason_path (str, optional): Location of the FLORIS jason
file. Defaults to ".".
Returns:
error (float): Error of model on test set
"""
# Create a dataset to test the model on
x_eval, y_eval = self.create_floris_dataset(
set_size, image_size, u_range=u_range, ti_range=ti_range,
yaw_range=yaw_range, floris_init_path=florisjason_path)
# Use generated data set to calculate the error of CNNwake when
# compared with the FLORIS output
test_error = self.error(x_eval, y_eval, device,
image_size, normalisation=12)
return test_error
def epoch_training(self, criterion, optimizer, dataloader, device):
"""
Trains the model for one epoch data provided by dataloader. The model
will be updated after each batch and the function will return the
train loss of the last batch
Args:
criterion (torch.nn.criterion): Loss function used to train model
optimizer (torch.optim.Optimizer): Optimizer for gradient descent
dataloader (torch.utils.DataLoader): Dataloader to store dataset
device (str): Device on which model/data is stored, cpu or cuda
Returns:
training loss (float): Loss of training set defined by criterion
"""
# For all training data in epoch
for real_images, label in dataloader:
# move data to device
real_images = real_images.to(device)
label = label.to(device)
# images need to be in correct shape: batch_size x 1 x 1 x 3
# compute reconstructions of flow-field using the CNN
outputs = self.forward(label)
# compute training reconstruction loss using the
# loss function set
train_loss = criterion(outputs, real_images)
optimizer.zero_grad() # Zero gradients of previous step
train_loss.backward() # compute accumulated gradients
optimizer.step() # Do optimizer step
# return training loss
return train_loss.item()
def load_model(self, path='.', device='cpu'):
"""
Function to load model from a pt file into this class.
Args:
path (str): path to saved model.
device (torch.device): Device to load onto, cpu or cuda
"""
# load the pretrained model
self.load_state_dict(torch.load(path, map_location=device))
def save_model(self, name='generator.pt'):
"""
Function to save current model paramters so that it can
be used again later. Needs to be saved with as .pt file
Args:
name (str): name of .pt file from which to load model
"""
# Save current model parameters
torch.save(self.state_dict(), name)

557
Code/CNNWake/FCC_model.py Normal file
View File

@ -0,0 +1,557 @@
import torch
import torch.nn as nn
import numpy as np
import random
import floris.tools as wfct
import torch.optim as optim
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader
from torch.optim import lr_scheduler
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
class FCNN(nn.Module):
"""
The class is the Neural Network that can predicts the power output of
wind turbine and the turbulent intensity (TI) at the turbine. The same
network architecture is used for both TI and power predict which
simplifies the code. The network uses the pytorch framwork and uses fully
connected layers. The methods of this class include the training of
the network, testing of the accuracy and generaton of training data.
The networks can be fine tuned via transfer learing if a specific park
layout is known, this will stongly improve the accuracy.
"""
def __init__(self, in_size, nr_neurons, out_size=1):
"""
init method that generates the network architecture using pytroch.
The number of input varibles can be changed incase more flow data is
available in the line segment upstream the turbine.
The nr_neurons defines the size of the given network. The output size
is set to 1 because the network only predicts either the power or TI.
In theory it should be able to do both the error was the high
therefore two networks are used.
Args:
in_size (int): Nr. of inputs, usually 42, 40 for wind speed
and the global ti and yaw angle of the turbine
nr_neurons (int): Nr. of neurons used in the layers, more
neurons means that
the network will have more parameters
out_size (int): Nr. of outputs in the last layer,
set to one if the NN only predicts a single value.
"""
super(FCNN, self).__init__()
# This defines the model architecture
self.disc = nn.Sequential(
# The linear layer is the fully connected layer
torch.nn.Linear(in_size, nr_neurons),
# LeakyReLU activation function after every fully
# connected layer
torch.nn.LeakyReLU(negative_slope=0.01),
torch.nn.Linear(nr_neurons, nr_neurons),
torch.nn.LeakyReLU(negative_slope=0.01),
torch.nn.Linear(nr_neurons, nr_neurons),
torch.nn.LeakyReLU(negative_slope=0.01),
torch.nn.Linear(nr_neurons, out_size),
)
def forward(self, x):
"""
Functions defines a forward pass though the network. Can be used for
a single input or a batch of inputs
Args:
x (torch.tensor): input tensor, to be passed through the network
Returns:
flow_fields (torch.tensor): Output of network
"""
# Use the architecture defined above for a forward pass
return self.disc(x)
def initialize_weights(self):
"""
Initilize weights using a xavier uniform distribution which has
helped training.
Loop over all modules, if module is a linear layer then
initialize weigths.
For more information about xavier initialization please read:
Understanding the difficulty of training deep feedforward neural
networks.
X. Glorot, und Y. Bengio. AISTATS , Volume 9 JMLR Proceedings,
249-256, 2010
"""
# for ever layer in model
if type(self) == nn.Linear:
# initialize weights using a xavier distribution
torch.nn.init.xavier_uniform(self.weight)
# initialize bias with 0.0001
self.bias.data.fill_(0.0001)
@staticmethod
def power_ti_from_FLORIS(x_position, y_position, yawn_angles,
wind_velocity, turbulent_int,
type='ti', nr_varabiles=40,
florisjason_path='.'):
"""
This function uses FLORIS to create the dataset to train the FCNN.
The wind speed along a line just upstream every wind turbine and
the corresponding TI or power output will be returned as numpy arrays.
Args:
x_position (list or numpy array): 1d array of the x postions of
the wind turbines in m.
y_position (list or numpy array): 1d array of the y postions of
the wind turbines in m.
yawn_angles (lisr numpy array): 1d array of the yaw angle of every
wind turbinein degree, from -30° to 30°
wind_velocity (float): Free stream wind velocity in m/s,
from 3 m/s to 12 m/s
turbulent_int (float): Turbulent intensity in percent ,
from 1.5% to 25%
type (str): Type of data that is returned, if set to power,
the power generated by every turbine is Returned. If set to
anything else, the func will return the TI
nr_varabiles (int): Nr of points along the line upstream the
turbine to take u values from. More points means that more wind
speeds are sampled from upstream the turbine. 40 was a good value
florisjason_path (string): Location of the FLORIS jason file
Returns:
numpy array: Final 2d array of flow field around the wind park.
U_list (2d np.array): array of size len(x_position) x 1 x
nr_varabiles + 2 where all the wind speeds upstream every
turbine are stored
ti_power_list (np.array): array of size len(x_position) x 1
where either all power or TI values of the turbines are stored
"""
# define the x and y length of a single cell in the array
# This is set by the standard value used in FLROIS wakes
dx = 18.4049079755
dy = 2.45398773006
# Set the maximum length of the array to be 3000m and 400m
# more than the maximum x and y position of the turbines
x_max = np.max(x_position) + 3005
y_max = np.max(y_position) + 400
# Number of cells in x and y needed to create a 2d array of
# the maximum size
Nx = int(x_max / dx)
Ny = int(y_max / dy)
# Init FLORIS from the jason file
wind_farm = wfct.floris_interface.FlorisInterface("FLORIS_input"
"_gauss.json")
# Set the x and y postions of the wind turbines
wind_farm.reinitialize_flow_field(layout_array=[x_position,
y_position])
# Set the yaw angle of every turbine
for _ in range(0, len(x_position)):
wind_farm.change_turbine([_], {'yaw_angle': yawn_angles[_],
"blade_pitch": 0.0})
# Set inlet wind speed and TI
wind_farm.reinitialize_flow_field(wind_speed=wind_velocity,
turbulence_intensity=turbulent_int)
# Calculate wind field
wind_farm.calculate_wake()
# Extract 2d slice from 3d domain at hub height
# This slice needs to have the same number of cells in x and y
# and same physical dimensions
cut_plane = wind_farm.get_hor_plane(
height=90, x_resolution=Nx, y_resolution=Ny, x_bounds=[0, x_max],
y_bounds=[0, y_max]).df.u.values.reshape(Ny, Nx)
# Calculate power generated by every turbine
power = wind_farm.get_turbine_power()
# Calculate local TI at every tribune
ti = wind_farm.get_turbine_ti()
# Initialize list to store all all the u values
# Number of turbines x 1 x number of values used + 2
U_list = np.zeros((len(x_position), 1, nr_varabiles + 2))
# Initialise list to store TI or u valurs
ti_power_list = np.zeros((len(x_position), 1))
# From the flow field generated by FLORIS, extract the wind speeds
# from a line 60 meter upstream the turbines
for i in range(len(x_position)):
# determine the x and y cells that the tubine center is at
turbine_cell = [int((x_position[i]) / dx),
int((y_position[i] - 200) / dy)]
# extract wind speeds along the rotor, 60 meters upstream
u_upstream_hub = cut_plane[
turbine_cell[1] + 45: turbine_cell[1] + 110,
turbine_cell[0] - 3]
# Do an running average, this is done because CNNwake has slight
# variations in the u predictions, also normalise the u values
u_average = [((u_upstream_hub[i - 1] +
u_upstream_hub[i] +
u_upstream_hub[i + 1]) / 3) / 12 for i in
np.linspace(1, 63, nr_varabiles, dtype=int)]
# append yaw which is normalised and ti
u_average = np.append(u_average, yawn_angles[i] / 30)
u_input_fcnn = np.append(u_average, turbulent_int)
U_list[i] = u_input_fcnn
# If type required is power then use power else
# use TI
if type == 'power':
ti_power_list[i] = power[i]
else:
ti_power_list[i] = ti[i]
# round values to 2 places
return np.round(U_list, 2), np.round(ti_power_list, 2)
@staticmethod
def create_ti_power_dataset(size, u_range, ti_range, yaw_range,
nr_varabiles=40, type='power',
floris_path='.'):
"""
This function will create a training or test set to train the power
or turbulent intensity (TI) prediction networks. The function will
use FLORIS to create the flowfield around 4 example wind parks
and saves the wind speed just upstream the wind rotor of every turbine
and the corresponding TI or power output. The wind speeds are along a
line which spans the entire diameter of the turbine blades and along
this line nr_varibles of points are sampled and the wind farm TI and
yaw angle of the corresponding turbine is added.
This allows the network to predict the power output of every turbine
under different inflow conditions or TI at every trubine.
Four different wind parks examples are used to generate the data,
this does not cover all possible flow fields
but delivers a good inital guess for the network.
The corresponding TI or power values are normalised by the maximum
value of the array, this will make all values to be between
0 and 1 which helps training.
Args:
size (int, optional): Nr of example flows generated and saved for
training. Defaults to 400.
u_range (list): Bound of u values [u_min, u_max] used
ti_range (list): Bound of TI values [TI_min, TI_max] used
yaw_range (list): Bound of yaw angles [yaw_min, yaw_max] used
nr_varabiles (int, optional): Nr. of values sampled along line.
Defaults to 40.
type (str, optional): If set to power, the power will be saved,
if set to anything else the TI at every turbine will be saved
Defaults to 'power'.
floris_path (str, optinal): Path to FLORIS jason file.
Returns:
x [torch tensor]: Tensor of size size*6 x 1 x nr_varabiles+2 where
all the flow data along line is stored. This will be the input
to the FCNN
y [torch tensor]: Tensor of size chuck_size*6 x 1 where all the
TI or pwoer data for every turbine is stored, this is what the
FCNN is trained to predict
"""
# 4 wind parks are used the generate data
# for every wind park generates 1/4 of the dataset
chuck_size = int(size/4)
# initialize empty numpy array to store 2d arrays
# and corresponding u, ti and yawn values
y = np.zeros((chuck_size * 4 * 6, 1, nr_varabiles + 2))
x = np.zeros((chuck_size * 6 * 4, 1))
# index to add the wind fields in the right postion
index = [i for i in range(0, size * 6, 6)]
# create train examples
print("generate FLORIS data")
# WIND PARK 1
for _ in range(0, chuck_size):
# sample u, ti and yaw from uniform distro
u_list = round(random.uniform(u_range[0], u_range[1]), 2)
ti_list = round(random.uniform(ti_range[0], ti_range[1]), 2)
yawlist = [round(random.uniform(yaw_range[0], yaw_range[1]), 2) for _ in range(0, 6)]
# get the wind speeds along line and corresponding TI or power
# from FLORIS for the wind park
u_list_hub, floris_power_ti = FCNN.power_ti_from_FLORIS(
[100, 300, 1000, 1300, 2000, 2300],
[300, 500, 300, 500, 300, 500],
yawlist, u_list, ti_list, type, nr_varabiles,
florisjason_path=floris_path)
# add u and power/TI in correct postion
y[index[_]: index[_ + 1], :, :] = u_list_hub
x[index[_]: index[_ + 1], :] = floris_power_ti
# WIND PARK 2
for _ in range(chuck_size, chuck_size * 2):
u_list = round(random.uniform(u_range[0], u_range[1]), 2)
ti_list = round(random.uniform(ti_range[0], ti_range[1]), 2)
yawlist = [round(random.uniform(yaw_range[0], yaw_range[1]), 2) for _ in range(0, 6)]
u_list_hub, floris_power_ti = FCNN.power_ti_from_FLORIS(
[100, 600, 1000, 1300, 2000, 2900],
[300, 300, 300, 300, 300, 500],
yawlist, u_list, ti_list, type, nr_varabiles)
y[index[_]: index[_ + 1], :, :] = u_list_hub
x[index[_]: index[_ + 1], :] = floris_power_ti
# WIND PARK 3
for _ in range(chuck_size * 2, chuck_size * 3):
u_list = round(random.uniform(u_range[0], u_range[1]), 2)
ti_list = round(random.uniform(ti_range[0], ti_range[1]), 2)
yawlist = [round(random.uniform(yaw_range[0], yaw_range[1]), 2) for _ in range(0, 6)]
u_list_hub, floris_power_ti = FCNN.power_ti_from_FLORIS(
[100, 100, 800, 1600, 1600, 2600],
[300, 500, 400, 300, 500, 400],
yawlist, u_list, ti_list, type, nr_varabiles)
y[index[_]: index[_ + 1], :, :] = u_list_hub
x[index[_]: index[_ + 1], :] = floris_power_ti
# WIND PARK 4
for _ in range(chuck_size * 3, chuck_size * 4 - 1):
u_list = round(random.uniform(u_range[0], u_range[1]), 2)
ti_list = round(random.uniform(ti_range[0], ti_range[1]), 2)
yawlist = [round(random.uniform(yaw_range[0], yaw_range[1]), 2) for _ in range(0, 6)]
u_list_hub, floris_power_ti = FCNN.power_ti_from_FLORIS(
[100, 300, 500, 1000, 1300, 1600],
[300, 500, 300, 300, 500, 400],
yawlist, u_list, ti_list, type, nr_varabiles)
y[index[_]: index[_ + 1], :, :] = u_list_hub
x[index[_]: index[_ + 1], :] = floris_power_ti
# transform into a pytroch tensor
x = torch.tensor(x[0:-6], dtype=torch.float)
y = torch.tensor(y[0:-6], dtype=torch.float)
print(f"Normalisation used: {torch.max(x)}")
# Normalise the power/TI by maximum value so that they are
# between 0-1
x = x / torch.max(x)
return y, x
def epoch_training(self, criterion, optimizer, dataloader, device):
"""
Trains the model for one epoch data provided by dataloader. The model
will be updated after each batch and the function will return the
train loss of the last batch
Args:
criterion (torch.nn.criterion): Loss function used to
train model
optimizer (torch.optim.Optimizer): Optimizer used for
gradient descent
dataloader (torch.utils.data.DataLoader): Dataloader for dataset
device (str): Device on which model and data is stored,
cpu or cuda
Returns:
training loss (float): Loss value of training set defined
by criterion
"""
# For all data in datalaoder
for power_ti, input_u in dataloader:
# one batch at a time, get network prediction
output = self(input_u.to(device))
# compute loss
train_loss = criterion(output.squeeze(), power_ti[:, 0].to(device))
self.zero_grad() # Zero the gradients
train_loss.backward() # Calc gradients
optimizer.step() # Do parameter update
return train_loss.item()
def learn_wind_park(self, x_postion, y_position, size, eval_size,
nr_varabiles=40, type='power',
device='cpu', nr_epochs=50,
batch_size=100, lr=0.003):
"""
EXPERIMENTAL FUNCTION; DOES NOT WORK YET, DO NOT USE!!!
This function is supposed to fine tune a already trained TI/Power
model on a specific wind park. This should further reduce the error
in predicting power or local TI. However, it currently increase the
error so there is something wrong. DO NOT USE!!!!
Args:
x_postion (list or numpy array): 1d array of the x positions of
the wind turbines in m.
y_position (list or numpy array): 1d array of the y positions of
the wind turbines in m.
size (list numpy array): Size of training set
eval_size (list numpy array): Size of test set
nr_varabiles (int): Nr of points along the line upstream the
turbine to take u values from. More points means that more
speeds are sampled from upstream the turbine. 40 was a good
type (str): Type of data that is returned, if set to power,
the power generated by every turbine is Returned. If set to
anything else, the func will return the TI
device (torch.device): Device to run the training on, cuda or cpu
nr_epochs (int): Nr. of training epochs
batch_size (int): Training batch size
lr (float): Model learning rate
Returns:
[Bool]: True if training was successful
"""
nr_values = int(((size + eval_size)*len(x_postion)))
# initialize empty numpy array to store 2d arrays and
# corresponding u, ti and yawn values
y = np.zeros((nr_values, 1, nr_varabiles + 2))
x = np.zeros((nr_values, 1))
print(nr_values)
print(len(x_postion))
print(int(nr_values/len(x_postion)))
index = [i for i in range(0, nr_values * 2, len(x_postion))]
# create train examples of the specified wind farm using FLORIS
print("generate FLORIS data")
for _ in range(0, int(nr_values/len(x_postion))):
u_list = round(random.uniform(3, 12), 2)
ti_list = round(random.uniform(0.015, 0.25), 2)
yawlist = [round(random.uniform(-30, 30), 2)
for _ in range(0, len(x_postion))]
u_list_hub, floris_power_ti = FCNN.power_ti_from_FLORIS(
x_postion, y_position, yawlist, u_list, ti_list, type,
nr_varabiles)
y[index[_]: index[_ + 1], :, :] = u_list_hub
x[index[_]: index[_ + 1], :] = floris_power_ti
x = torch.tensor(x, dtype=torch.float)
y = torch.tensor(y, dtype=torch.float)
print(f"Normalisation used: {torch.max(x)}")
x = x / torch.max(x)
x_train = x[0:size * len(x_postion)]
y_train = y[0:size * len(x_postion)]
x_eval = x[-eval_size*len(x_postion):]
y_eval = y[-eval_size*len(x_postion):]
print(x_eval.size(), x_train.size())
dataset = TensorDataset(x_train, y_train.float())
# generate dataload for training
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
optimizer = optim.Adam(self.parameters(), lr=lr)
scheduler_gen = lr_scheduler.ReduceLROnPlateau(optimizer, 'min',
factor=0.6, patience=4,
verbose=True)
# use L2 norm as criterion
criterion = nn.MSELoss()
# init to list to store error
error_list = []
# Train model on data
for _ in range(nr_epochs): # train model
self.train() # set model to training mode
loss = self.epoch_training(criterion, optimizer,
dataloader, device)
self.eval() # set model to evaluation
# evaluation on validation set
val_error = self.error(y_eval, x_eval, device)
# if error has not decreased over the past 4 epochs
# decrease the lr by a factor of 0.6
scheduler_gen.step(val_error)
error_list.append(val_error)
print(f" Epoch: {_:.0f}, Training loss: {loss:.4f},"
f" Validation error: {val_error:.2f}")
# plot the val error over the epochs
plt.plot(range(nr_epochs), error_list)
plt.show()
return True
def error(self, x_eval, y_eval, device='cpu'):
"""
Function to calculate the error between the networks
predictions and the actual output. The x and y values
need to be generated using the create_ti_power_dataset
function. The error will be the mean percentage difference
between all values predicted by the network and the actual
values
Args:
x_eval (torch tensor): Tensor of all flow, ti and yaw values
for different turbines, this the the model input.
y_eval (torch tensor): Tensor of all TI or power outputs as
calculated by floris for the corresponding flow field in x
device (str, optional): Device where the model is stored on.
Defaults to 'cpu'.
Returns:
error (float): percentage error
"""
error_list = []
# Do forward pass of the x data
model_predict = self.forward(x_eval.to(device))
for n in range(0, len(y_eval)):
# sometimes the power prediction is zero, this will give
# an error of inf due to divide by zero in step below.
# Therefore filter out very small power here
if abs(y_eval.detach().cpu().numpy()[n]) < 0.01:
continue
else:
# calculate error
power_error = abs(y_eval.detach().cpu().numpy()[n] -
model_predict[n].detach().cpu().numpy()) / (
y_eval.detach().cpu().numpy()[n] + 1e-8)
error_list.append(power_error * 100)
return np.mean(error_list)
def load_model(self, path='.', device='cpu'):
"""
Function to load model from a pt file into this class.
Args:
path (str): path to saved model.
device (torch.device): Device to load onto, cpu or cuda
"""
# Load a previously trained model
self.load_state_dict(torch.load(path, map_location=device))
def save_model(self, name='generator.pt'):
"""
Function to save current model paramters so that it can
be used again later. Needs to be saved with as .pt file
Args:
name (str): name of .pt file from which to load model
"""
torch.save(self.state_dict(), name)

BIN
Code/CNNWake/FCNN_TI.pt Normal file

Binary file not shown.

BIN
Code/CNNWake/FCNN_TI_old.pt Normal file

Binary file not shown.

View File

@ -0,0 +1,258 @@
{
"description": "Example FLORIS Input file",
"farm": {
"description": "Example 2x2 Wind Farm",
"name": "farm_example_2x2",
"properties": {
"__comment__": "specified_wind_height of -1 uses the first turbine's hub height; After initialization, specified_wind_height is a free parameter.",
"air_density": 1.225,
"layout_x": [
0.0
],
"layout_y": [
0.0
],
"specified_wind_height": -1,
"turbulence_intensity": [
0.06
],
"wind_direction": [
270.0
],
"wind_shear": 0.12,
"wind_speed": [
9.0
],
"wind_veer": 0.0,
"wind_x": [
0
],
"wind_y": [
0
]
},
"type": "farm"
},
"floris_version": "v2.0.0",
"logging": {
"console": {
"enable": false,
"level": "None"
},
"file": {
"enable": false,
"level": "INFO"
}
},
"name": "floris_input_file_Example",
"turbine": {
"description": "NREL 5MW",
"name": "nrel_5mw",
"properties": {
"TSR": 8.0,
"blade_count": 3,
"blade_pitch": 0.0,
"generator_efficiency": 1.0,
"hub_height": 90.0,
"ngrid": 5,
"pP": 1.88,
"pT": 1.88,
"power_thrust_table": {
"power": [
0.0,
0.0,
0.1780851,
0.28907459,
0.34902166,
0.3847278,
0.40605878,
0.4202279,
0.42882274,
0.43387274,
0.43622267,
0.43684468,
0.43657497,
0.43651053,
0.4365612,
0.43651728,
0.43590309,
0.43467276,
0.43322955,
0.43003137,
0.37655587,
0.33328466,
0.29700574,
0.26420779,
0.23839379,
0.21459275,
0.19382354,
0.1756635,
0.15970926,
0.14561785,
0.13287856,
0.12130194,
0.11219941,
0.10311631,
0.09545392,
0.08813781,
0.08186763,
0.07585005,
0.07071926,
0.06557558,
0.06148104,
0.05755207,
0.05413366,
0.05097969,
0.04806545,
0.04536883,
0.04287006,
0.04055141
],
"thrust": [
1.19187945,
1.17284634,
1.09860817,
1.02889592,
0.97373036,
0.92826162,
0.89210543,
0.86100905,
0.835423,
0.81237673,
0.79225789,
0.77584769,
0.7629228,
0.76156073,
0.76261984,
0.76169723,
0.75232027,
0.74026851,
0.72987175,
0.70701647,
0.54054532,
0.45509459,
0.39343381,
0.34250785,
0.30487242,
0.27164979,
0.24361964,
0.21973831,
0.19918151,
0.18131868,
0.16537679,
0.15103727,
0.13998636,
0.1289037,
0.11970413,
0.11087113,
0.10339901,
0.09617888,
0.09009926,
0.08395078,
0.0791188,
0.07448356,
0.07050731,
0.06684119,
0.06345518,
0.06032267,
0.05741999,
0.05472609
],
"wind_speed": [
2.0,
2.5,
3.0,
3.5,
4.0,
4.5,
5.0,
5.5,
6.0,
6.5,
7.0,
7.5,
8.0,
8.5,
9.0,
9.5,
10.0,
10.5,
11.0,
11.5,
12.0,
12.5,
13.0,
13.5,
14.0,
14.5,
15.0,
15.5,
16.0,
16.5,
17.0,
17.5,
18.0,
18.5,
19.0,
19.5,
20.0,
20.5,
21.0,
21.5,
22.0,
22.5,
23.0,
23.5,
24.0,
24.5,
25.0,
25.5
]
},
"rloc": 0.5,
"rotor_diameter": 126.0,
"tilt_angle": 0.0,
"use_points_on_perimeter": false,
"yaw_angle": 0.0
},
"type": "turbine"
},
"type": "floris_input",
"wake": {
"description": "wake",
"name": "wake_default",
"properties": {
"combination_model": "sosfs",
"deflection_model": "gauss",
"parameters": {
"wake_deflection_parameters": {
"gauss": {
"dm": 1.0,
"eps_gain": 0.2,
"use_secondary_steering": true
}
},
"wake_turbulence_parameters": {
"crespo_hernandez": {
"ai": 0.8,
"constant": 0.5,
"downstream": -0.32,
"initial": 0.1
}
},
"wake_velocity_parameters": {
"gauss_legacy": {
"calculate_VW_velocities": true,
"eps_gain": 0.2,
"ka": 0.38,
"kb": 0.004,
"use_yaw_added_recovery": true
}
}
},
"turbulence_model": "crespo_hernandez",
"velocity_model": "gauss_legacy"
},
"type": "wake"
}
}

0
Code/CNNWake/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,7 @@
from .CNN_model import Generator
from .FCC_model import FCNN
from .superposition import super_position, FLORIS_farm_power, CNNWake_farm_power
from .train_FCNN import train_FCNN_model
from .train_CNN import train_CNN_model
from .visualise import Compare_CNN_FLORIS, visualize_farm
from .optimisation import FLORIS_wake_steering, CNNwake_wake_steering

View File

@ -0,0 +1,201 @@
from scipy.optimize import minimize
import numpy as np
import torch
import time
import floris.tools as wfct
from .superposition import CNNWake_farm_power, FLORIS_farm_power
from .CNN_model import Generator
from .FCC_model import FCNN
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
def CNNwake_wake_steering(x_position, y_position, initial_yaw, wind_velocity,
turbulent_int, CNN_generator, Power_model, TI_model,
device, bounds, tolerance):
"""
Function will optimise the yaw angle of a specific wind farm for
a given inlet wind speed and TI using CNNwake's wind farm function.
Please ensure that the x position are in ascending order and every
turbine is placed at least 300 above 0 in the y direction. This is done
to ensure that no wake is lost at the edge of the domain.
Args:
x_position (list or numpy array): 1d array of the x postions of
the wind turbines in m.
y_position (list or numpy array): 1d array of the y postions of
the wind turbines in m.
initial_yaw (list or numpy array): 1d array of inital yaw angle
of every wind turbine in degree, set to 0
wind_velocity (float): Free stream wind velocity in m/s,
ensure NNa are trained on this wind speed
turbulent_int (float): Turbulent intensity in percent ,
ensure NNs are trained on this TI
CNN_generator (Generator): CNN to predict the wake of a single
turbine, ensure it is trained and set to validation mode
Power_model (Generator): FCNN to predict the power generated
by a turbine, ensure it is trained and set to validation mode
TI_model (Generator): FCNN to predict the local TI of a
turbine, ensure it is trained and set to validation mode
device (torch.device): Device to store and run the neural network
on, either cpu or cuda
bounds (list): Yaw angle bounds for optimisation [min_yaw, max_yaw]
tolerance (float): Relative solver tolerance
Returns:
opt_yaw.x (np.array): Optimal yaw angle
opt_yaw.func (float): Optimal power output
time_taken (float): Time taken for optimisation
"""
# Set all NNs to evaluation mode
CNN_generator.eval()
Power_model.eval()
TI_model.eval()
# Run a few check to ensure that optimisation will work
# Check if there are same number of turbines defined in
# x, y and yaw anf´gle arrays
assert len(x_position) == len(y_position)
assert len(y_position) == len(initial_yaw)
# check if x_list in ascending order, if this assert fails
# ensure that x goes from smallest to largest
if len(x_position) > 1:
assert np.any(np.diff(np.array(x_position)) > 0)
# Check if all the NNs work as expected
assert CNN_generator(torch.tensor([[
4, 0.1, 20]]).float().to(device)).size() == \
torch.Size([1, 1, 163, 163])
assert TI_model(torch.tensor([
i for i in range(0, 42)]).float().to(device)).size() == \
torch.Size([1])
assert Power_model(torch.tensor([
i for i in range(0, 42)]).float().to(device)).size() == \
torch.Size([1])
# create a list of tuples of bounds for the optimizer
bounds_list = [(bounds[0], bounds[1]) for _ in range(0, len(x_position))]
init_t = time.time() # start timer
# Using scipy.optimize function to find the optimal yaw setting by calling
# CNNWake_farm_power many times with different yaw angles. Ensure that all
# arguments are given in the correct order
opt_yaw = minimize(
CNNWake_farm_power, initial_yaw,
args=(x_position, y_position, wind_velocity, turbulent_int,
CNN_generator, Power_model, TI_model, device), method='SLSQP',
bounds=bounds_list, options={'ftol': tolerance, 'eps': 0.1,
'disp': False})
# find time taken for optimisation
time_taken = time.time() - init_t
return np.round(opt_yaw.x, 2), abs(opt_yaw.fun), time_taken
def FLORIS_wake_steering(x_position, y_position, initial_yaw, wind_velocity,
turbulent_int, bounds, tolerance, floris_path='./'):
"""
Function will optimise the yaw angle of a specific wind farm for
a given inlet wind speed and TI using FLORIS.
Please ensure that the x position are in ascending order and every
turbine is placed at least 300 above 0 in the direction. This is done
to ensure that no wake is lost at the edge of the domain.
Args:
x_position (list or numpy array): 1d array of the x postions of
the wind turbines in m.
y_position (list or numpy array): 1d array of the y postions of
the wind turbines in m.
initial_yaw (list or numpy array): 1d array of inital yaw angle
of every wind turbine in degree, set to 0
wind_velocity (float): Free stream wind velocity in m/s,
ensure NNa are trained on this wind speed
turbulent_int (float): Turbulent intensity in percent ,
ensure NNs are trained on this TI
bounds (list): Yaw angle bounds for optimisation [min, max]
tolerance (float): Relative solver tolerance
floris_path (str): Path to FLORIS jason file
Returns:
floris_opti.x (np.array): Optimal yaw angle
floris_opti.func (float): Optimal power output
time_taken (float): Time taken for optimisation
"""
# Check if there are same number of turbines defined in
# x, y and yaw anf´gle arrays
assert len(x_position) == len(y_position)
assert len(y_position) == len(initial_yaw)
# create a list of tuples of bounds for the optimizer
bounds = [(bounds[0], bounds[1]) for _ in range(0, len(x_position))]
# This variable is used to sum up all the power generated by turbines
floris_park = 0
# Check if path to FLORIS jason file is correct by testing if it can
# open it
try:
floris_park = wfct.floris_interface.FlorisInterface(
floris_path + "FLORIS_input_gauss.json")
except FileNotFoundError:
print('No FLORIS_input_gauss.jason file found at this lcoation, '
'please specfiy the path to this file')
init_t = time.time() # Start timer
# Using scipy.optimize function to find the optimal yaw setting by calling
# FLORIS_farm_power many times with different yaw angles. Ensure that all
# arguments are given in the correct order
floris_opti = minimize(
FLORIS_farm_power, initial_yaw,
args=(x_position, y_position, wind_velocity,
turbulent_int, floris_park),
method='SLSQP', bounds=bounds,
options={'ftol': tolerance, 'eps': 0.1,
'disp': False})
time_taken = time.time() - init_t
return np.round(floris_opti.x, 2), abs(floris_opti.fun), time_taken
if __name__ == '__main__':
# To run individual CNNWake files, the imports are not allowed to be
# relative. Instead of: from .CNN_model import Generator
# it needs to be: from CNN_model import Generator for all CNNWake imports
# select device to run model on
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Load and set up all NNs
CNN_generator = Generator(3, 30).to(device)
CNN_generator.load_model('./trained_models/CNN_FLOW.pt', device=device)
Power_model = FCNN(42, 300, 1).to(device)
Power_model.load_state_dict(torch.load('./trained_models/FCNN_POWER.pt',
map_location=device))
TI_model = FCNN(42, 300, 1).to(device)
TI_model.load_state_dict(torch.load('./trained_models/FCNN_TI.pt',
map_location=device))
# Use optimisation to find best yaw angle
yaw1, power1, timing1 = CNNwake_wake_steering(
[100, 100, 1000, 1000],
[300, 800, 300, 800],
[0, 0, 0, 0], 10.6, 0.09, CNN_generator, Power_model, TI_model,
device, [-30, 30], 1e-07)
print(f"CNNwake optimized yaw abgle: {yaw1}")
# Find FLORIS best yaw abgle
yaw, power, timing = FLORIS_wake_steering(
[100, 100, 1000, 1000],
[300, 800, 300, 800],
[0, 0, 0, 0], 10.6, 0.09, [-30, 30], 1e-07)
print(f"FLORIS optimized yaw abgle: {yaw}")

BIN
Code/CNNWake/power_model.pt Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,276 @@
import torch
from torch.backends import cudnn
import matplotlib.pyplot as plt
import numpy as np
import time
import floris.tools as wfct
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
def super_position(farm_array, turbine_array, turbine_postion,
hub_speed, wind_velocity, sp_model="SOS"):
"""
Generate super-position of a turbine wind field and a farm wind field.
The turbine wind field is superimposed onto the wind farm flow field using
different super-postion models. The recommended model is the root sum of
squares (SOS), more information about super-postion can be found in this
paper: https://doi.org/10.1088/1742-6596/749/1/012003
Args:
farm_array (numpy array): 2d wind field of whole wind farm
turbine_array (numpy array): 2d wind field around wind turbine
turbine_postion (numpy array): x and y cell number of wind turbine
in the global array [x_cell, y_cell]
hub_speed (float): u velocity at turbine hub in m/s
wind_U_turbine (float): wind speed at turbine hub
wind_velocity (float): free stream wind speed
sp_model (str, optional): Select model to be used for the
super-positioning Defaults to "SOS".
Returns:
numpy array: 2d wind field of whole wind farm with flow
field around turbine superimposed
"""
# normalize wind by the free stream and hub height speed
turbine_u = turbine_array/hub_speed
farm_u = farm_array/wind_velocity
# Define the start and end coordinates of the turbine wake
# in the global wind park array
x_start = turbine_postion[0]
x_end = turbine_postion[0]+turbine_u.shape[0]
y_start = turbine_postion[1]
y_end = turbine_postion[1]+turbine_u.shape[1]
if sp_model == "SOS":
# For SOS model with one turbine, the equation is:
# u = 1 - sqrt((1 - u_1)^2 + (1 - u_2)^2)
sos1 = np.square(1 - turbine_u)
sos2 = np.square(1 - farm_u)
# place the SOS superpostion in the correct location of the farm array
farm_array[y_start:y_end, x_start:x_end] = (1 - np.sqrt(
sos1 + sos2[y_start:y_end, x_start:x_end])) * wind_velocity
# farm_array now includes the velocity field of the turbine
return farm_array
elif sp_model == "linear":
# For SOS model with one turbine, the equation is:
# u = 1 - ((1 - u_1) + (1 - u_2))
sos1 = 1 - turbine_u
sos2 = 1 - farm_u
# place the linear superpostion in the correct
# location of the farm array
farm_array[
turbine_postion[1]:turbine_postion[1]+sos1.shape[1],
turbine_postion[0]:turbine_postion[0]+sos1.shape[0]] = \
(1 - (sos1 + sos2[
turbine_postion[1]:turbine_postion[1]+sos1.shape[1],
turbine_postion[0]:turbine_postion[0]+sos1.shape[0]]))\
* wind_velocity
# farm_array now includes the velocity field of the turbine
return farm_array
elif sp_model == "largest_deficit":
# u = min(u_1, u_2)
# place the SOS super postion in the correct location of the farm array
farm_array[
turbine_postion[1]:turbine_postion[1]+turbine_u.shape[1],
turbine_postion[0]:turbine_postion[0]+turbine_u.shape[0]]\
= np.minimum(turbine_u,
farm_u[y_start:y_end,
x_start:x_end]) * wind_velocity
# farm_array now includes the velocity field of the turbine
return farm_array
else:
# other models to be added
raise Exception('No super position model selected, please'
' either select: SOS, linear or largest_deficit')
def CNNWake_farm_power(
yawn_angles, x_position, y_position, wind_velocity, turbulent_int,
CNN_generator, Power_model, TI_model, device,
ti_normalisation=0.30000001, power_normalisation=4834506):
"""
Calculates the power output of the wind farm using the NN.
The generated power is returned as negative number for the minimization.
The individual wakes of the turbines are calculated using the CNN and
superimposed onto the wind farm flow field using a super-position model.
The energy produced by the turbines are calculated using another fully
connected network from the flow data just upstream the turbine.
Please ensure that the x position are in ascending order and every
turbine is placed at least 300 above 0 in the direction. This is done
to ensure that no wake is lost at the edge of the domain.
Args:
yawn_angles (list): 1d array of the yaw angle of every wind turbine
in degree, from -30° to 30°
x_position (list): 1d array of the x postions of the wind
turbines in meters.
y_position (list): 1d array of the y postions of the wind
turbines in meters.
wind_velocity (float): Free stream wind velocity in m/s,
from 3 m/s to 12 m/s
turbulent_int (float): Turbulent intensity in percent,
from 1.5% to 25%
CNN_generator (Generator): CNN to predict the wake of a single
turbine, ensure it is trained and set to validation mode
Power_model (Generator): FCNN to predict the power generated
by a turbine, ensure it is trained and set to validation mode
TI_model (Generator): FCNN to predict the local TI of a
turbine, ensure it is trained and set to validation mode
device (torch.device): Device to store and run the neural network
on, either cpu or cuda
ti_normalisation (float): Normalisation of the TI training set
power_normalisation (float): Normalisation of the power training set
Returns:
power float: negative power output
"""
# Define the x and y length of a single cell in the array
# This is set by the standard value used in FLORIS wakes
dx = 18.4049079755
dy = 2.45398773006
# Set the maximum length of the array to be 3000m and 400m
# more than the maximum x and y position of the wind park
# If a larger physical domain was used change adapt the values
x_max = np.max(x_position) + 3000
y_max = np.max(y_position) + 300
# Number of cells in x and y needed to create a 2d array of
# that is x_max x y_max using dx, dy values
Nx = int(x_max/dx)
Ny = int(y_max/dy)
# Initialise a 2d array of the wind park with the
# inlet wind speed
farm_array = np.ones((Ny, Nx)) * wind_velocity
# round yaw angle
yawn_angles = np.round(yawn_angles, 2)
# Initialise array to store power and TI for every turbine
power_CNN = []
ti_CNN = []
with torch.no_grad(): # Ensure no gradients are calculated
# For every wind turbine
for i in range(len(x_position)):
# determine the x and y cells that the turbine center is at
turbine_cell = [int((x_position[i])/dx),
int((y_position[i] - 200)/dy)]
# extract wind speeds along the rotor, 60 meters upstream
u_upstream_hub = farm_array[
turbine_cell[1] + 45: turbine_cell[1] + 110, turbine_cell[0] - 3]
# Do an running average, this is done because CNNwake has slight
# variations in the u predictions, also normalise the u values
u_list_hub = [
((u_upstream_hub[i-1] + u_upstream_hub[i] +
u_upstream_hub[i+1])/3)/12 for i in np.linspace(
5, len(u_upstream_hub)-5, 40, dtype=int)]
# append yaw angle and normalised it, also append ti
u_list_hub = np.append(u_list_hub, yawn_angles[i]/30)
u_list_hub = np.append(u_list_hub, turbulent_int)
# The local TI does not change from inlet TI if the turbine
# is not covered by a wake, therefore check if if all values
# in u_list_hub are the same -> means no wake coverage
# Local TI also depends on yaw, if yaw is less than 12° and
# turbine is not in wake -> use inlet TI for local TI
if np.allclose(
u_list_hub[0], u_list_hub[0:-3], rtol=1e-02, atol=1e-02)\
and abs(u_list_hub[-2]) < 0.4:
ti = turbulent_int
# If turbine is in wake or yaw angle is larger use FCNN to find
# local TI
else:
# Use FCNN forward pass to predict TI
ti = TI_model((torch.tensor(u_list_hub).float().to(device))).detach().cpu().numpy() * ti_normalisation
# regulate TI to ensure it is not to different from free stream
if ti < turbulent_int*0.7:
ti = turbulent_int * 1.5
# clip ti values to max and min trained
ti = np.clip(ti, 0.015, 0.25).item(0)
ti_CNN.append(ti) # Save ti value
# Replace global/inlet TI in u_list with local TI
u_list_hub[-1] = ti
# Use FCNN to predcit power generated by turbine
turbine_energy = Power_model(torch.tensor(u_list_hub).float().to(device)).detach().cpu().numpy() * power_normalisation
power_CNN.append(turbine_energy) # Save power
# Find the mean wind speed upstream the turbine
hub_speed = np.round(np.mean(u_upstream_hub), 2)
# Create Array of array to pass it to CNN
turbine_condition = [[hub_speed, ti, yawn_angles[i]]]
# Use CNN to calculate wake of individual trubine
turbine_field = CNN_generator(torch.tensor(turbine_condition).float().to(device))
# Since CNN output is normalised,
# mutiply by 12 and create a numpy array
turbine_field = turbine_field[0][0].detach().cpu().numpy() * 12
# Place wake of indivual turbine in the farm_array
farm_array = super_position(
farm_array, turbine_field, turbine_cell,
hub_speed, wind_velocity, sp_model="SOS")
# Return the value negative of power generated
return -sum(power_CNN).item(0)
def FLORIS_farm_power(
yawn_angles, x_position, y_position, wind_velocity,
turbulent_int, floris_park):
"""
Function to generate the power output of a wind farm defined by
the x, y and yaw angles of every turbine in the farm.
The function will only use FLORIS to calcaute the power and
returnes the power as a negtive value which is needed for
the minimisation.
Args:
yawn_angles (list): Yaw angle of every turbine in the wind park
x_position (list): All x locations of the turbines
y_position (list): All y locations of the turbines
wind_velocity (float): Inlet wind speed
turbulent_int (float): Inlet turbulent intensity
floris_park (floris.tools.FlorisInterface): Floris interface
loads in data from jason file
Returns:
power (float): negative power generated by wind park
"""
# Round yaw angle input
yawn_angles = np.round(yawn_angles, 2)
# Set the x and y postions of the wind turbines
floris_park.reinitialize_flow_field(layout_array=[x_position,
np.array(y_position)])
# Set the yaw angle of every turbine
for _ in range(0, len(x_position)):
floris_park.change_turbine([_],
{'yaw_angle': yawn_angles[_],
"blade_pitch": 0.0})
# Set inlet wind speed and TI
floris_park.reinitialize_flow_field(wind_speed=wind_velocity,
turbulence_intensity=turbulent_int)
# Calculate wind field
floris_park.calculate_wake()
# Calculate power generated by every turbine
power = floris_park.get_turbine_power()
# Return the sum of all power per turbine but as negative
# value for the optimisation
return -sum(power)

133
Code/CNNWake/train_CNN.py Normal file
View File

@ -0,0 +1,133 @@
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader
from torch.optim import lr_scheduler
from .CNN_model import Generator
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
def train_CNN_model(
nr_filters, nr_epochs, learing_rate, batch_size,
train_size, val_size, image_size, device, u_range,
ti_range, yaw_range, model_name, nr_workers=0, floris_path="."):
"""
Create a new model and train it for a certain number of epochs using a
newly generated dataset. Hyper-parameters such as model size or lr can be
changed as input to the function.
After training the model error for all epochs is plotted and the model
performance will be evaluated on a test set. Finally, the model
will saved as the model_name which needs to add as .pt file
Args:
nr_filters (int): Nr. of filters used for the conv layers
nr_epochs (int): Nr. of training epochs
learing_rate (float): Model learning rate
batch_size (int): Training batch size
train_size (int): Size of the generated training set
val_size (int): Size of the generated validation set
image_size (int): Size of the data set images, needs to match the
model output size for the current model this is 163 x 163
device (torch.device): Device to run the training on, cuda or cpu
u_range (list): Bound of u values [u_min, u_max] used
ti_range (list): Bound of TI values [TI_min, TI_max] used
yaw_range (list): Bound of yaw angles [yaw_min, yaw_max] used
model_name (str): Name of the trained saved model (needs be .pt)
nr_workers (int, optional): Nr. of worker to load data. Defaults to 0.
floris_path (str, optinal): Path to FLORIS jason file.
Returns:
gen (Generator): Trained model
loss (float): training loss defined by the loss function
val_error (float): Percentage error on the validation set
"""
# The current inputs are: u, ti and yaw. If more are
# used please change this input var
nr_input_var = 3
# create a generator of the specified size
gen = Generator(nr_input_var, nr_filters).to(device)
# create a datasets from the data generated by FLORIS
x_train, y_train = gen.create_floris_dataset(
size=train_size, image_size=image_size, u_range=u_range,
ti_range=ti_range, yaw_range=yaw_range, floris_init_path=floris_path,
curl=False)
x_eval, y_eval = gen.create_floris_dataset(
size=val_size, image_size=image_size, u_range=u_range,
ti_range=ti_range, yaw_range=yaw_range, floris_init_path=floris_path,
curl=False)
dataset = TensorDataset(y_train.unsqueeze(1), x_train.float())
# generate dataload for training
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True,
num_workers=nr_workers)
# init the weights of the generator
gen.initialize_weights()
# set up and optimizer and learing rate scheduler using hyperparameters
optimizer = optim.Adam(gen.parameters(), lr=learing_rate)
scheduler_gen = lr_scheduler.ReduceLROnPlateau(
optimizer, 'min', factor=0.6, patience=4, verbose=True)
# use L2 norm as criterion
criterion = nn.MSELoss()
# init to list to store error
error_list = []
for _ in range(nr_epochs): # train model
gen.train() # set model to training mode
# use method to train for one epoch
loss = gen.epoch_training(criterion, optimizer, dataloader, device)
gen.eval() # set model to evaluation
# evaluation on validation set
val_error = gen.error(x_eval, y_eval,
device, image_size=image_size,
normalisation=12)
# if error has not decreased over the past 4
# epochs decrease the lr by a factor of 0.6
scheduler_gen.step(val_error)
error_list.append(val_error)
print(f" Epoch: {_:.0f},"
f" Training loss: {loss:.4f},"
f" Validation error: {val_error:.2f}")
print("Finished training")
# save model
gen.save_model(model_name)
# plot the val error over the epochs
plt.plot(range(nr_epochs), error_list)
plt.show()
return gen, loss, val_error
if __name__ == '__main__':
# To run individual CNNWake files, the imports are not allowed to be
# relative. Instead of: from .CNN_model import Generator
# it needs to be: from CNN_model import Generator for all CNNWake imports
# Set device used for training
devices = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# Train a new model with the given parameters
train_CNN_model(
nr_filters=16, nr_epochs=25, learing_rate=0.003, batch_size=50,
train_size=200, val_size=30, image_size=163, device=devices,
u_range=[3, 12], ti_range=[0.015, 0.25], yaw_range=[-30, 30],
model_name='generator.pt'
)

129
Code/CNNWake/train_FCNN.py Normal file
View File

@ -0,0 +1,129 @@
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset, DataLoader
from torch.optim import lr_scheduler
from FCC_model import FCNN
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
def train_FCNN_model(
nr_neurons, input_size, nr_epochs, learing_rate, batch_size,
train_size, val_size, u_range, ti_range, yaw_range, model_name,
type='power', device='cpu', nr_workers=0, floris_path="."):
"""
Create a new model and train it for a certain number of epochs using a
newly generated dataset. Hyper-parameters such as model size or lr can be
changed as input to the function.
After training the model error over all epochs is plotted and the model
performance will be evaluated on a unseen test set. Finally, the model
will saved as the model_name which needs to add as .pt file
Args:
nr_filters (int): Nr. of filters used for the conv layers
nr_epochs (int): Nr. of training epochs
learing_rate (float): Model learing rate
batch_size (int): Training batch size
train_size (int): Size of the generated training set
val_size (int): Size of the generated validation set
u_range (list): Bound of u values [u_min, u_max] used
ti_range (list): Bound of TI values [TI_min, TI_max] used
yaw_range (list): Bound of yaw angles [yaw_min, yaw_max] used
model_name (str): Name of the trained saved model (needs to be .pt)
image_size (int): Size of the data set images, needs to match the
model output size for the current model this is 163
device (torch.device): Device to run the training on, cuda or cpu
nr_workers (int, optional): Nr. of workers to load data. Defaults to 0.
floris_path (str, optinal): Path to FLORIS jason file.
Returns:
gen (Generator): Trained model
loss (float): training loss defined by the loss function
val_error (float): Percentage error on the validation set
"""
# The current inputs are: u, ti and yaw. If more are used please
# change this input var
model_input_size = input_size + 2
# create a generator of the specified size
model = FCNN(model_input_size, nr_neurons, 1).to(device)
# create a datasets from the data generated by FLORIS
x_train, y_train = model.create_ti_power_dataset(
size=train_size, u_range=u_range, ti_range=ti_range,
yaw_range=yaw_range, nr_varabiles=input_size, type=type,
floris_path=floris_path)
x_eval, y_eval = model.create_ti_power_dataset(
size=val_size, u_range=u_range, ti_range=ti_range,
yaw_range=yaw_range, nr_varabiles=input_size, type=type,
floris_path=floris_path)
dataset = TensorDataset(y_train, x_train.float())
# generate dataload for training
dataloader = DataLoader(
dataset, batch_size=batch_size, shuffle=True, num_workers=nr_workers)
# init the weights of the generator
model.initialize_weights()
# set up and optimizer and learing rate scheduler using hyperparameters
optimizer = optim.Adam(model.parameters(), lr=learing_rate)
scheduler_gen = lr_scheduler.ReduceLROnPlateau(
optimizer, 'min', factor=0.6, patience=4, verbose=True)
# use L2 norm as criterion
criterion = nn.MSELoss()
# init to list to store error
error_list = []
for _ in range(nr_epochs): # train model
model.train() # set model to training mode
loss = model.epoch_training(criterion, optimizer, dataloader, device)
model.eval() # set model to evaluation
# evaluation on validation set
val_error = model.error(x_eval, y_eval, device)
# if error has not decreased over the past 4 epochs decrease
# the lr by a factor of 0.6
scheduler_gen.step(val_error)
error_list.append(val_error)
print(f" Epoch: {_:.0f},"
f" Training loss: {loss:.4f},"
f" Validation error: {val_error:.2f}")
# save model
model.save_model(model_name)
# plot the val error over the epochs
plt.plot(range(nr_epochs), error_list)
plt.show()
return model, loss, val_error
if __name__ == '__main__':
# To run indivual CNNWake files, the imports are not allowed to be
# relative. Instead of: from .FCC_model import FCNN
# it needs to be: from FCC_model import FCNN, for all CNNWake imports
# Set device used for training
devices = torch.device("cpu" if torch.cuda.is_available() else "cpu")
# Train a FCNN to predict power
train_FCNN_model(
nr_neurons=20, input_size=20, nr_epochs=150, learing_rate=0.003,
batch_size=30, train_size=50, val_size=40, u_range=[3, 12],
ti_range=[0.015, 0.25], yaw_range=[-30, 30],
model_name='power_model.pt', type='power', device=devices)

319
Code/CNNWake/visualise.py Normal file
View File

@ -0,0 +1,319 @@
import torch
import matplotlib.pyplot as plt
import numpy as np
import time
import floris.tools as wfct
from .superposition import super_position
__author__ = "Jens Bauer"
__copyright__ = "Copyright 2021, CNNwake"
__credits__ = ["Jens Bauer"]
__license__ = "MIT"
__version__ = "1.0"
__email__ = "jens.bauer20@imperial.ac.uk"
__status__ = "Development"
def visualize_turbine(plane, domain_size, nr_points, title="", ax=None):
"""
Function to plot the flow field around a single turbine
Args:
plane (2d numpy array): Flow field around turbine
domain_size (list or numpy array): x and y limits of the domain,
the first two values correspond to min and max of x and
similar for the y values [x_min, x_max, y_min, y_max]
nr_points (list or numpy array): Nr. of points in the array
title (str, optional): Title of the graph. Defaults to "".
ax (ax.pcolormesh, optional): Pyplot subplot class,
adds the plot to this location.
Returns:
ax.pcolormesh: Image of the flow field
"""
# create mesh grid for plotting
x = np.linspace(domain_size[0], domain_size[1], nr_points[0])
y = np.linspace(domain_size[2], domain_size[3], nr_points[1])
x_mesh, y_mesh = np.meshgrid(x, y)
# Plot the cut-through
im = ax.pcolormesh(x_mesh, y_mesh, plane, shading='auto', cmap="coolwarm")
ax.set_title(title)
# Make equal axis
ax.set_aspect("equal")
return im
def visualize_farm(
plane, nr_points, size_x, size_y, title="", ax=None, vmax=False):
"""
Function to plot flow-field around a wind farm.
Args:
plane (2d numpy array): Flow field of wind farm
nr_points (list or np array): List of nr of points in x and y
size_x (int): Size of domain in x direction (km)
size_y (int): Size of domain in y direction (km)
title (str, optional): Title of the plot. Defaults to "".
ax (ax.pcolormesh, optional): Pyplot subplot class, adds the plot
to this location.
vmax (bool, optional): Maximum value to plot. If false,
the max value of the plane is used a vmax
Returns:
ax.pcolormesh: Image of the flow field around the wind farm
"""
x = np.linspace(0, size_x, nr_points[0]) # this is correct!
y = np.linspace(0, size_y, nr_points[1])
x_mesh, y_mesh = np.meshgrid(x, y)
# if no vmax is set, use the maximum of plane
if vmax is False:
vmax = np.max(plane)
# Plot the cut-through
im = ax.pcolormesh(x_mesh, y_mesh, plane,
shading='auto', cmap="coolwarm", vmax=vmax)
ax.set_title(title)
# Make equal axis
ax.set_aspect("equal")
return im
def Compare_CNN_FLORIS(
x_position, y_position, yawn_angles, wind_velocity, turbulent_int,
CNN_generator, Power_model, TI_model, device,
florisjason_path='', plot=False):
"""
Generates the wind field around a wind park using the neural networks.
The individual wakes of the turbines are calculated using thee CNN and
superimposed onto the wind farm flow field using a super-position model.
The energy produced by the turbines are calcuated using another fully
connected network from the flow data just upstream the turbine.
The functions generates the same wind park flow field using FLORIS so that
the two solutions can be compared when plot = True is set.
Args:
x_position (list): 1d array of x locations of the wind turbines in m.
y_position (list): 1d array of y locations of the wind turbines in m.
yawn_angles (list): 1d array of yaw angles of every wind turbine.
wind_velocity (float): Free stream wind velocity in m/s.
turbulent_int (float): Turbulent intensity in percent.
device (torch.device): Device to store and run the neural network on,
cpu or cuda
florisjason_path (string): Location of the FLORIS jason file
plot (bool, optional): If True, the FLORIS and CNN solution will
be plotted and compared.
Returns:
numpy array: Final 2d array of flow field around the wind park.
"""
# Define the x and y length of a single cell in the array
# This is set by the standard value used in FLORIS wakes
dx = 18.4049079755
dy = 2.45398773006
# Set the maximum length of the array to be 3000m and 400m
# more than the maximum x and y position of the wind park
# If a larger physical domain was used change adapt the values
x_max = np.max(x_position) + 3000
y_max = np.max(y_position) + 300
# Number of cells in x and y needed to create a 2d array of
# that is x_max x y_max using dx, dy values
Nx = int(x_max / dx)
Ny = int(y_max / dy)
# Initialise a 2d array of the wind park with the
# inlet wind speed
farm_array = np.ones((Ny, Nx)) * wind_velocity
# set up FLORIS model
floris_model = wfct.floris_interface.FlorisInterface(
florisjason_path + "FLORIS_input_gauss.json")
floris_model.reinitialize_flow_field(
layout_array=[x_position, np.array(y_position)])
for _ in range(0, len(x_position)):
floris_model.change_turbine([_], {'yaw_angle': yawn_angles[_],
"blade_pitch": 0.0})
floris_model.reinitialize_flow_field(wind_speed=wind_velocity,
turbulence_intensity=turbulent_int)
start_t = time.time()
# Calcuate using FLORIS and extract 2d flow field
floris_model.calculate_wake()
print(f"Time taken for FLORIS to generate"
f" wind park: {time.time() - start_t:.3f}")
floris_plane = floris_model.get_hor_plane(
height=90, x_resolution=Nx, y_resolution=Ny, x_bounds=[0, x_max],
y_bounds=[0, y_max]).df.u.values.reshape(Ny, Nx)
floris_power = floris_model.get_turbine_power()
floris_ti = floris_model.get_turbine_ti()
# print(floris_power, floris_ti)
power_CNN = []
ti_CNN = []
t = time.time()
with torch.no_grad():
# Do CNNwake cautions
for i in range(len(x_position)):
# determine the x and y cells that the turbine center is at
turbine_cell = [int((x_position[i]) / dx),
int((y_position[i] - 200) / dy)]
t1 = time.time()
# extract wind speeds along the rotor, 60 meters upstream
u_upstream_hub = farm_array[
turbine_cell[1] + 45: turbine_cell[1] + 110,
turbine_cell[0] - 3]
# Do an running average, this is done because CNNwake has slight
# variations in the u predictions, also normalise the u values
u_power = [
((u_upstream_hub[i - 1] + u_upstream_hub[i] +
u_upstream_hub[i + 1]) / 3) / 12 for
i in np.linspace(5, 55, 40, dtype=int)]
u_power = np.append(u_power, yawn_angles[i] / 30)
u_power = np.append(u_power, turbulent_int)
# The local TI does not change from inlet TI if the turbine
# is not covered by a wake, therefore check if if all values
# in u_list_hub are the same -> means no wake coverage
# Local TI also depends on yaw, if yaw is less than 12° and
# turbine is not in wake -> use inlet TI for local TI
if np.allclose(u_power[0], u_power[0:-3],
rtol=1e-02, atol=1e-02) and abs(u_power[-2]) < 0.4:
# print("Turbine in free stream, set ti to normal")
ti = turbulent_int
else:
ti = TI_model((torch.tensor(u_power).float().to(device))).detach().cpu().numpy() * 0.30000001192092896
# regulate TI to ensure it is not to different from free stream
if ti < turbulent_int * 0.7:
# print(f"TI REGULATED 1 AT {i}")
ti = turbulent_int * 1.5
# clip ti values to max and min trained
ti = np.clip(ti, 0.015, 0.25).item(0)
ti_CNN.append(ti)
u_power[-1] = ti
energy = Power_model(torch.tensor(u_power).float().to(device)).detach().cpu().numpy() * 4834506
power_CNN.append(energy[0])
hub_speed = np.round(np.mean(u_upstream_hub), 2)
turbine_condition = [[hub_speed, ti, yawn_angles[i]]]
turbine_field = CNN_generator(torch.tensor(turbine_condition).float().to(device))
# Use CNN to calculate wake of individual trubine
# Since CNN output is normalised,
# mutiply by 12 and create a numpy array
turbine_field = turbine_field[0][0].detach().cpu().numpy() * 12
# Place wake of indivual turbine in the farm_array
farm_array = super_position(
farm_array, turbine_field, turbine_cell, hub_speed,
wind_velocity, sp_model="SOS")
# print information
print(f"Time taken for CNNwake to generate wind park: {time.time() - t:.3f}")
print(f"CNNwake power prediction error: "
f"{100 * np.mean(abs(np.array(floris_power) - np.array(power_CNN)) / np.array(floris_power)):.2f} %")
print(f"CNNwake TI prediction error: {100 * np.mean(abs(np.array(floris_ti) - np.array(ti_CNN)) / np.array(floris_ti)):.2f} %")
print(f"APWP error: {100 * np.mean(abs(floris_plane - farm_array) / np.max(floris_plane)):.2f}")
if plot:
plt.rcParams.update({'font.size': 16})
# Plot wake fields of both wind farms and error field
fig, axarr = plt.subplots(3, 1, sharex=True, figsize=(20, 49))
im1 = visualize_farm(farm_array, nr_points=[Nx, Ny], size_x=x_max,
size_y=y_max, title="CNNwake", ax=axarr[0])
im2 = visualize_farm(floris_plane, nr_points=[Nx, Ny], size_x=x_max,
size_y=y_max, title="FLORIS", ax=axarr[1])
im3 = visualize_farm(
(100 * abs(floris_plane - farm_array) / np.max(floris_plane)),
nr_points=[Nx, Ny], size_x=x_max, size_y=y_max,
title="Pixel wise percentage error ", ax=axarr[2], vmax=20)
col1 = fig.colorbar(im1, ax=axarr[0])
col1.set_label('m/s', labelpad=15, y=1.06, rotation=0)
col2 = fig.colorbar(im2, ax=axarr[1])
col2.set_label('m/s', labelpad=15, y=1.06, rotation=0)
col3 = fig.colorbar(im3, ax=axarr[2])
col3.set_label('%', labelpad=11, y=0.9, rotation=0)
axarr[2].set_xlabel('m', fontsize=15)
axarr[0].set_ylabel('m', labelpad=9, rotation=0, y=0.4, fontsize=15)
axarr[1].set_ylabel('m', labelpad=9, rotation=0, y=0.4, fontsize=15)
axarr[2].set_ylabel('m', labelpad=9, rotation=0, y=0.4, fontsize=15)
# Plot TI and Power of every turbine for FLORIS adn CNNNwake
fig, axarr = plt.subplots(2, figsize=(9, 9))
axarr[0].plot(range(1, len(x_position) + 1),
np.array(power_CNN)/1.e06, 'o--', label="CNNwake")
axarr[0].plot(range(1, len(x_position) + 1),
np.array(floris_power)/1.e06, 'o--', label="FLORIS")
axarr[1].plot(range(1, len(x_position) + 1),
np.array(ti_CNN), 'o--', label="CNNwake")
axarr[1].plot(range(1, len(x_position) + 1),
floris_ti, 'o--', label="FLORIS")
axarr[0].set_ylabel('Power output [MW]', fontsize=15)
axarr[1].set_ylabel('Local TI [%]', fontsize=15)
axarr[1].set_xlabel('Turbine Nr.', rotation=0, fontsize=15)
axarr[1].legend()
axarr[0].legend()
plt.show()
return farm_array, floris_plane
if __name__ == '__main__':
# To run individual CNNWake files, the imports are not allowed to be
# relative. Instead of: from .superposition import super_position
# it needs to be: from superposition import super_position, for all CNNWake imports
# also import all NNs
from CNN_model import Generator
from FCC_model import FCNN
from superposition import super_position
# Set up/load all NNs
device = torch.device("cpu" if torch.cuda.is_available() else "cpu")
CNN_generator = Generator(3, 30).to(device)
CNN_generator.load_model('./trained_models/CNN_FLOW.pt', device=device)
CNN_generator = CNN_generator.to()
CNN_generator.eval()
# the first forward pass is super slow so do it outside loop and use the
# output for a simple assert test
example_out = CNN_generator(torch.tensor([[4, 0.1, 20]]).float().to(device))
assert example_out.size() == torch.Size([1, 1, 163, 163])
Power_model = FCNN(42, 300, 1).to(device)
Power_model.load_state_dict(torch.load('./trained_models/FCNN_POWER.pt', map_location=device))
Power_model.eval()
# the first forward pass is super slow so do it outside loop and use the
# output for a simple assert test
energy = Power_model(torch.tensor([i for i in range(0, 42)]).float().to(device))
assert energy.size() == torch.Size([1])
TI_model = FCNN(42, 300, 1).to(device)
TI_model.load_state_dict(torch.load('./trained_models/FCNN_TI.pt', map_location=device))
TI_model.eval()
# the first forward pass is super slow so do it outside loop and use the
# output for a simple assert test
TI = TI_model(torch.tensor([i for i in range(0, 42)]).float().to(device))
assert TI.size() == torch.Size([1])
# Compare a single wind farm, this will show the wake, energy and local TI
# for every turbine and compare it to FLORIS
farm, a = Compare_CNN_FLORIS([100, 100, 700, 700, 1200, 1200],
[300, 800, 1300, 550, 1050, 300],
[0, 0, 0, 0, 0, 0, 0], 11.6, 0.06,
CNN_generator, Power_model,
TI_model, device, plot=True)