mirror of
https://github.com/soanagno/wakenet.git
synced 2025-08-06 19:42:51 +08:00
Add files via upload
This commit is contained in:

committed by
GitHub

parent
157fc2c1d8
commit
bd4a43535e
330
Code/CNNWake/CNN_model.py
Normal file
330
Code/CNNWake/CNN_model.py
Normal 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
557
Code/CNNWake/FCC_model.py
Normal 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
BIN
Code/CNNWake/FCNN_TI.pt
Normal file
Binary file not shown.
BIN
Code/CNNWake/FCNN_TI_old.pt
Normal file
BIN
Code/CNNWake/FCNN_TI_old.pt
Normal file
Binary file not shown.
258
Code/CNNWake/FLORIS_input_gauss.json
Normal file
258
Code/CNNWake/FLORIS_input_gauss.json
Normal 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
0
Code/CNNWake/__init__.py
Normal file
BIN
Code/CNNWake/__pycache__/CNN_model.cpython-39.pyc
Normal file
BIN
Code/CNNWake/__pycache__/CNN_model.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Code/CNNWake/__pycache__/FCC_model.cpython-39.pyc
Normal file
BIN
Code/CNNWake/__pycache__/FCC_model.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Code/CNNWake/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
Code/CNNWake/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Code/CNNWake/__pycache__/superposition.cpython-39.pyc
Normal file
BIN
Code/CNNWake/__pycache__/superposition.cpython-39.pyc
Normal file
Binary file not shown.
BIN
Code/CNNWake/__pycache__/train_FCNN.cpython-39.pyc
Normal file
BIN
Code/CNNWake/__pycache__/train_FCNN.cpython-39.pyc
Normal file
Binary file not shown.
7
Code/CNNWake/in/__init__.py
Normal file
7
Code/CNNWake/in/__init__.py
Normal 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
|
201
Code/CNNWake/optimisation.py
Normal file
201
Code/CNNWake/optimisation.py
Normal 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
BIN
Code/CNNWake/power_model.pt
Normal file
Binary file not shown.
BIN
Code/CNNWake/power_model_old.pt
Normal file
BIN
Code/CNNWake/power_model_old.pt
Normal file
Binary file not shown.
276
Code/CNNWake/superposition.py
Normal file
276
Code/CNNWake/superposition.py
Normal 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
133
Code/CNNWake/train_CNN.py
Normal 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
129
Code/CNNWake/train_FCNN.py
Normal 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
319
Code/CNNWake/visualise.py
Normal 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)
|
Reference in New Issue
Block a user