├── IMAGES ├── Jensen's wake model.png └── j.enconman.2019.06.082.svg ├── LICENSE ├── README.md ├── WindFarmGenetic.py └── main.py /IMAGES/Jensen's wake model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JuXinglong/WFLOP_SUGGA_Python/27ae25a40be7fe2258894a741013aa46e9d1e4b3/IMAGES/Jensen's wake model.png -------------------------------------------------------------------------------- /IMAGES/j.enconman.2019.06.082.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 20 | 22 | DOI 23 | 24 | 25 | DOI 26 | 27 | 29 | 10.1016/j.enconman.2019.06.082 30 | 31 | 32 | 10.1016/j.enconman.2019.06.082 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 JuXinglong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Energy Conversion and Management - Wind farm layout optimization based on support vector regression guided genetic algorithm with consideration of participation among landowners 2 | 3 | # Wind farm layout optimization problem (WFLOP) SUGGA Python toolbox 4 | 5 | ## Reference 6 | [1] Xinglong Ju, Feng Liu, Li Wang, and Wei-Jen Lee. "Wind farm layout optimization based on support vector regression guided genetic algorithm with consideration of participation among landowners." *Energy Conversion and Management* 196 (2019): 1267-1281. [![DOI](/IMAGES/j.enconman.2019.06.082.svg)](https://doi.org/10.1016/j.enconman.2019.06.082)
7 | 8 | If you have any questions, please email Feng Liu (*fliu0@mgh.harvard.edu*), or Xinglong Ju (*xinglong.ju@mavs.uta.edu*). 9 | 10 |

11 | Jensen's wake model.png
12 | Jensen's wake model 13 |

14 | 15 | ## SUGGA - WFLOP Python toolbox guide 16 | ### Import necessary libraries 17 | ```python 18 | import numpy as np 19 | import pandas as pd 20 | import WindFarmGenetic # wind farm layout optimization using genetic algorithms classes 21 | from datetime import datetime 22 | import os 23 | from sklearn.svm import SVR 24 | import pickle 25 | ``` 26 | 27 | ### Wind farm settings and algorithm settings 28 | ```python 29 | # parameters for the genetic algorithm 30 | elite_rate = 0.2 31 | cross_rate = 0.6 32 | random_rate = 0.5 33 | mutate_rate = 0.1 34 | 35 | wt_N = 25 # number of wind turbines 15, 20, or 25 36 | # NA_loc_array : not available location array, the index starting from 1 37 | 38 | # L1 : wind farm, cells 121(inclusive) to 144(inclusive) 39 | NA_loc_array = np.arange(121, 145, 1) 40 | # 41 | # # L2 42 | # NA_loc_array = np.arange(61, 85, 1) 43 | # 44 | # # L3 45 | # NA_loc_array = np.concatenate((np.arange(11, 144, 12), np.arange(12, 145, 12))) 46 | # 47 | # # L4 48 | # NA_loc_array = np.concatenate((np.arange(6, 144, 12), np.arange(7, 145, 12))) 49 | # 50 | # # L5 51 | # NA_loc_array = np.concatenate((np.arange(41, 105, 12), np.arange(42, 105, 12), 52 | # np.arange(43, 105, 12), 53 | # np.arange(44, 105, 12))) 54 | # 55 | # # L6 56 | # NA_loc_array = np.concatenate((np.arange(1, 28, 12), np.arange(2, 28, 12), 57 | # np.arange(12, 37, 12), 58 | # np.arange(11, 37, 12), 59 | # np.arange(109, 145, 12), np.arange(119, 145, 12), 60 | # np.arange(110, 145, 12), 61 | # np.arange(120, 145, 12), 62 | # )) 63 | # 64 | # # L7 65 | # NA_loc_array = np.arange(133, 145, 1) 66 | # 67 | # # L8 68 | # NA_loc_array = np.arange(61, 73, 1) 69 | # 70 | # # L9 71 | # NA_loc_array = np.arange(12, 145, 12) 72 | # 73 | # # L10 74 | # NA_loc_array = np.arange(6, 145, 12) 75 | # 76 | # # L11 77 | # NA_loc_array = np.concatenate((np.arange(42, 105, 12), 78 | # np.arange(43, 105, 12))) 79 | # 80 | # # L12 81 | # NA_loc_array = np.array((1, 2, 11, 12, 13, 24, 121, 132, 133, 134, 143, 144)) 82 | 83 | # convert numpy array to list, datatype convert 84 | NA_loc = NA_loc_array.tolist() 85 | 86 | # L0 87 | # NA_loc = [] 88 | 89 | 90 | population_size = 120 # how many layouts in a population 91 | iteration_times = 200 # how many iterations in a genetic algorithm run 92 | 93 | n_inits = 100 # number of initial populations n_inits >= run_times 94 | run_times = 100 # number of different initial populations 95 | 96 | 97 | 98 | # wind farm size, cells 99 | cols_cells = 12 # number of cells each row 100 | rows_cells = 12 # number of cells each column 101 | cell_width = 77.0 * 3 # unit : m 102 | 103 | # all data will be save in data folder 104 | data_folder = "data" 105 | if not os.path.exists(data_folder): 106 | os.makedirs(data_folder) 107 | ``` 108 | 109 | 110 | ### Create a WindFarmGenetic object 111 | ```python 112 | # create an WindFarmGenetic object. Specify the number of rows and the number columns of the wind farm land. N is the number of wind turbines. 113 | # NA_loc is the not available locations on the wind farm land. Landowners does not want to participate in the wind farm. 114 | # pop_size: how many individuals in the population 115 | # iteration: iteration times of the genetic algorithm 116 | wfg = WindFarmGenetic.WindFarmGenetic(rows=rows_cells, cols=cols_cells, N=wt_N, NA_loc=NA_loc, pop_size=population_size, 117 | iteration=iteration_times,cell_width=cell_width, elite_rate=elite_rate, 118 | cross_rate=cross_rate, random_rate=random_rate, mutate_rate=mutate_rate) 119 | # Specify the wind distribution 120 | # wind distribution is discrete (number of wind speeds) by (number of wind directions) 121 | # wfg.init_1_direction_1_N_speed_13() 122 | # file name to store the wind power distribution SVR model 123 | # svr_model_filename = 'svr_1s1d_N_13.svr' 124 | 125 | # wfg.init_4_direction_1_speed_13() 126 | # svr_model_filename = 'svr_1s4d_13.svr' 127 | 128 | wfg.init_6_direction_1_speed_13() 129 | # svr_model_filename = 'svr_1s6d_13.svr' 130 | ``` 131 | 132 | ### Generate initial populations 133 | ```python 134 | # initial population saved folder 135 | init_pops_data_folder = "{}/init_data".format(data_folder) 136 | if not os.path.exists(init_pops_data_folder): 137 | os.makedirs(init_pops_data_folder) 138 | # generate initial populations to start with and store them 139 | # in order to start from the same initial population for different methods 140 | # so it is fair to compare the final results 141 | 142 | for i in range(n_inits): 143 | wfg.gen_init_pop_NA() 144 | wfg.save_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder,i), "{}/init_{}_NA.dat".format(init_pops_data_folder,i)) 145 | ``` 146 | 147 | ### Create results folder 148 | ```python 149 | # results folder 150 | # adaptive_best_layouts_N60_9_20190422213718.dat : best layout for AGA of run index 9 151 | # result_CGA_20190422213715.dat : run time and best eta for CGA method 152 | results_data_folder = "data/results" 153 | if not os.path.exists(results_data_folder): 154 | os.makedirs(results_data_folder) 155 | # if cg,ag,sg folder does not exist, create these folders. Folders to store the running results 156 | # cg: convertional genetic algorithm 157 | # ag: adaptive genetic algorithm 158 | # sg: support vector regression guided genetic algorithm 159 | cg_result_folder = "{}/cg".format(results_data_folder) 160 | if not os.path.exists(cg_result_folder): 161 | os.makedirs(cg_result_folder) 162 | 163 | ag_result_folder = "{}/ag".format(results_data_folder) 164 | if not os.path.exists(ag_result_folder): 165 | os.makedirs(ag_result_folder) 166 | 167 | sg_result_folder = "{}/sg".format(results_data_folder) 168 | if not os.path.exists(sg_result_folder): 169 | os.makedirs(sg_result_folder) 170 | 171 | # resul_arr: run_times by 2 , the first column is the run time in seconds for each run and the second column is the conversion efficiency for the run 172 | result_arr = np.zeros((run_times, 2), dtype=np.float32) 173 | ``` 174 | 175 | ### Run conventional genetic algorithm (CGA) 176 | ```python 177 | # CGA: Conventional genetic algorithm 178 | for i in range(0, run_times): # run times 179 | print("run times {} ...".format(i)) 180 | # load initial population 181 | wfg.load_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder,i), "{}/init_{}_NA.dat".format(init_pops_data_folder,i)) 182 | # run the conventional genetic algorithm and return run time and conversion efficiency 183 | run_time, eta = wfg.conventional_genetic_alg(i,result_folder=cg_result_folder) 184 | result_arr[i, 0] = run_time 185 | result_arr[i, 1] = eta 186 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 187 | # save the run time and etas to a file 188 | filename = "{}/result_conventional_{}.dat".format(cg_result_folder,time_stamp) 189 | np.savetxt(filename, result_arr, fmt='%f', delimiter=" ") 190 | ``` 191 | 192 | ### Run adaptive genetic algorithm (AGA) 193 | ```python 194 | # AGA: adaptive genetic algorithm 195 | for i in range(0,run_times): # run times 196 | print("run times {} ...".format(i)) 197 | wfg.load_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder,i),"{}/init_{}_NA.dat".format(init_pops_data_folder,i)) 198 | run_time, eta = wfg.adaptive_genetic_alg(i,result_folder=ag_result_folder) 199 | result_arr[i, 0] = run_time 200 | result_arr[i, 1] = eta 201 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 202 | filename = "{}/result_adaptive_{}.dat".format(ag_result_folder,time_stamp) 203 | np.savetxt(filename, result_arr, fmt='%f', delimiter=" ") 204 | ``` 205 | 206 | ### Run support vector regression guided genetic algorithm (SUGGA) 207 | #### Generate wind distribution surface 208 | ```python 209 | n_mc_samples = 10000 # svr train data, number of layouts to average 210 | 211 | wds_data_folder = "{}/wds".format(data_folder) 212 | if not os.path.exists(wds_data_folder): 213 | os.makedirs(wds_data_folder) 214 | # mc : monte-carlo 215 | 216 | # number of layouts to generate as the training data for regression 217 | # to build the power distribution surface 218 | 219 | # mc_layout.dat file stores layouts only with 0s and 1s. 0 means no turbine here. 1 means one turbine here. 220 | # mc_layout_NA.dat file stores layouts with 0s, 1s and 2s. 2 means no turbine and not available for turbine. 221 | # These two files are used to generate wind power distribution. 222 | # Each file has 10000 lines. Each line is layout. 223 | # gen_mc_grid_with_NA_loc function generates these two files. 224 | train_mc_layouts, train_mc_layouts_NA = WindFarmGenetic.LayoutGridMCGenerator.gen_mc_grid_with_NA_loc(rows_cells, 225 | cols_cells, 226 | n_mc_samples, 227 | wt_N, NA_loc, 228 | "{}/mc_layout.dat".format(wds_data_folder), 229 | "{}/mc_layout_NA.dat".format(wds_data_folder)) 230 | 231 | 232 | # wfg.init_1_direction_1_N_speed_13() 233 | # file name to store the wind power distribution SVR model 234 | # svr_model_filename = 'svr_1s1d_N_13.svr' 235 | 236 | # wfg.init_4_direction_1_speed_13() 237 | # svr_model_filename = 'svr_1s4d_13.svr' 238 | 239 | # wfg.init_6_direction_1_speed_13() 240 | svr_model_filename = 'svr_1s6d_13.svr' 241 | 242 | 243 | # load Monte-Carlo layouts from a text file. 10000 random layouts 244 | layouts = np.genfromtxt("{}/mc_layout.dat".format(wds_data_folder), delimiter=" ", dtype=np.int32) 245 | # generate the location index coordinate and average power output at each location index coordinate 246 | # location index coordinate : in the cells, the cell with index 1 has location index (0,0) and the cell 2 has (1,0) 247 | # store the location index coordinate in x.dat and average power in y.dat 248 | wfg.mc_gen_xy_NA(rows=rows_cells, cols=cols_cells, layouts=layouts, n=n_mc_samples, N=wt_N, xfname="{}/x.dat".format(wds_data_folder), 249 | yfname="{}/y.dat".format(wds_data_folder)) 250 | 251 | 252 | # read index location coordinates 253 | x_original = pd.read_csv("{}/x.dat".format(wds_data_folder), header=None, nrows=rows_cells * cols_cells, delim_whitespace=True, dtype=np.float32) 254 | x_original = x_original.values 255 | 256 | # read the power output of each index location coordinate 257 | y_original = pd.read_csv("{}/y.dat".format(wds_data_folder), header=None, nrows=rows_cells * cols_cells, delim_whitespace=True, dtype=np.float32) 258 | y_original = y_original.values.flatten() 259 | 260 | # create a SVR object and specify the kernal and other parameters 261 | svr_model = SVR(kernel='rbf', C=2000.0, gamma=0.3, epsilon=.1) 262 | # build the SVR power distribution model 263 | svr_model.fit(x_original, y_original) 264 | 265 | # save the SVR model to a file 266 | pickle.dump(svr_model, open("{}/{}".format(wds_data_folder,svr_model_filename), 'wb')) 267 | 268 | # This is how to load SVR model from a file 269 | # svr_model = pickle.load(open("{}/{}".format(wds_data_folder,svr_model_filename), 'rb')) 270 | ``` 271 | #### Run SUGGA method 272 | ```python 273 | #SUGGA: support vector regression guided genetic algorithm 274 | for i in range(0, run_times): # run times 275 | print("run times {} ...".format(i)) 276 | wfg.load_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder,i),"{}/init_{}_NA.dat".format(init_pops_data_folder,i)) 277 | run_time, eta = wfg.sugga_genetic_alg(i,svr_model=svr_model,result_folder=sg_result_folder) 278 | result_arr[i, 0] = run_time 279 | result_arr[i, 1] = eta 280 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 281 | filename = "{}/result_sugga_{}.dat".format(sg_result_folder,time_stamp) 282 | np.savetxt(filename, result_arr, fmt='%f', delimiter=" ") 283 | ``` 284 | -------------------------------------------------------------------------------- /WindFarmGenetic.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import matplotlib.patches as patches 5 | import time 6 | from datetime import datetime 7 | 8 | __version__ = "1.0.0" 9 | 10 | 11 | class WindFarmGenetic: 12 | elite_rate = 0.2 # elite rate: parameter for genetic algorithm 13 | cross_rate = 0.6 # crossover rate: parameter for genetic algorithm 14 | random_rate = 0.5 # random rate: parameter for genetic algorithm 15 | mutate_rate = 0.1 # mutation rate: parameter for genetic algorithm 16 | turbine = None 17 | pop_size = 0 # population size : how many individuals in a population 18 | N = 0 # number of wind turbines 19 | rows = 0 # how many cell rows the wind farm are divided into 20 | cols = 0 # how many colus the wind farm land are divided into 21 | iteration = 0 # how many iterations the genetic algorithm run 22 | NA_loc=None # not available, not usable locations index list (the index starts from 1) 23 | cell_width = 0 # cell width 24 | cell_width_half = 0 # half cell width 25 | 26 | # constructor of the class 27 | def __init__(self, rows=21, cols=21, N=0,NA_loc=None, pop_size=100, iteration=200,cell_width=0, elite_rate=0.2, 28 | cross_rate=0.6, random_rate=0.5, mutate_rate=0.1): 29 | self.turbine = GE_1_5_sleTurbine() 30 | self.rows = rows 31 | self.cols = cols 32 | self.N = N 33 | self.pop_size = pop_size 34 | self.iteration = iteration 35 | 36 | self.cell_width = cell_width 37 | self.cell_width_half = cell_width * 0.5 38 | 39 | self.elite_rate = elite_rate 40 | self.cross_rate = cross_rate 41 | self.random_rate = random_rate 42 | self.mutate_rate = mutate_rate 43 | 44 | self.init_pop = None 45 | self.init_pop_NA = None 46 | self.init_pop_nonezero_indices = None 47 | self.NA_loc=NA_loc 48 | return 49 | 50 | 51 | # choose wind distribution : 1 direction, 1 speed 52 | def init_1_direction_1_N_speed_13(self): 53 | self.theta = np.array([0], dtype=np.float32) 54 | self.velocity = np.array([13.0], dtype=np.float32) 55 | self.f_theta_v = np.array([[1.0]], dtype=np.float32) 56 | return 57 | 58 | # choose wind distribution : 4 directions, 1 speed 59 | def init_4_direction_1_speed_13(self): 60 | self.theta = np.array( 61 | [0, 3 * np.pi / 6.0, 6 * np.pi / 6.0, 9 * np.pi / 6.0], dtype=np.float32) # 1.0/4 62 | self.velocity = np.array([13.0], dtype=np.float32) # 1 63 | self.f_theta_v = np.array([[0.25], [0.25], [0.25], [0.25]], dtype=np.float32) 64 | return 65 | 66 | # choose wind distribution : 6 direction, 1 speed 67 | def init_6_direction_1_speed_13(self): 68 | self.theta = np.array([0, np.pi / 3.0, 2 * np.pi / 3.0, 3 * np.pi / 3.0, 4 * np.pi / 3.0, 5 * np.pi / 3.0], 69 | dtype=np.float32) # 0.2, 0,3 0.2 0. 1 0.1 0.1 70 | self.velocity = np.array([13.0], dtype=np.float32) # 1 71 | self.f_theta_v = np.array([[0.2], [0.3], [0.2], [0.1], [0.1], [0.1]], dtype=np.float32) 72 | return 73 | 74 | 75 | # the total cost 76 | def cost(self, N): 77 | return 1.0 * N * (2.0 / 3.0 + 1.0 / 3.0 * math.exp(-0.00174 * N ** 2)) 78 | 79 | # generate initial population 80 | def gen_init_pop_NA(self): 81 | self.init_pop,self.init_pop_NA = LayoutGridMCGenerator.gen_pop_with_NA_loc(rows=self.rows, cols=self.cols,NA_loc=self.NA_loc, n=self.pop_size, N=self.N) 82 | self.init_pop_nonezero_indices = np.zeros((self.pop_size, self.N), dtype=np.int32) 83 | for ind_init_pop in range(self.pop_size): 84 | ind_indices = 0 85 | for ind in range(self.rows * self.cols): 86 | if self.init_pop[ind_init_pop, ind] == 1: 87 | self.init_pop_nonezero_indices[ind_init_pop, ind_indices] = ind 88 | ind_indices += 1 89 | return 90 | 91 | # save initial population 92 | def save_init_pop_NA(self, fname,fname_NA): 93 | np.savetxt(fname, self.init_pop, fmt='%d', delimiter=" ") 94 | np.savetxt(fname_NA, self.init_pop_NA, fmt='%d', delimiter=" ") 95 | return 96 | 97 | 98 | # load initial population 99 | def load_init_pop_NA(self, fname,fname_NA): 100 | self.init_pop = np.genfromtxt(fname, delimiter=" ", dtype=np.int32) 101 | self.init_pop_NA = np.genfromtxt(fname_NA, delimiter=" ", dtype=np.int32) 102 | self.init_pop_nonezero_indices = np.zeros((self.pop_size, self.N), dtype=np.int32) 103 | for ind_init_pop in range(self.pop_size): 104 | ind_indices = 0 105 | for ind in range(self.rows * self.cols): 106 | if self.init_pop[ind_init_pop, ind] == 1: 107 | self.init_pop_nonezero_indices[ind_init_pop, ind_indices] = ind 108 | ind_indices += 1 109 | return 110 | 111 | # calculate total rate power 112 | def cal_P_rate_total(self): 113 | f_p = 0.0 114 | for ind_t in range(len(self.theta)): 115 | for ind_v in range(len(self.velocity)): 116 | f_p += self.f_theta_v[ind_t, ind_v] * self.turbine.P_i_X(self.velocity[ind_v]) 117 | return self.N * f_p 118 | 119 | 120 | # generate the location index coordinate and average power output at each location index coordinate 121 | # location index coordinate : in the cells, the cell with index 1 has location index (0,0) and the cell 2 has (1,0) 122 | # store the location index coordinate in x.dat and average power in y.dat 123 | def mc_gen_xy_NA(self, rows, cols, layouts, n, N, xfname, yfname): 124 | layouts_cr = np.zeros((rows * cols, 2), dtype=np.int32) # layouts column row index 125 | n_copies = np.sum(layouts, axis=0) 126 | layouts_power = np.zeros((n, rows * cols), dtype=np.float32) 127 | self.mc_fitness(pop=layouts, rows=rows, cols=cols, pop_size=n, N=N, lp=layouts_power) 128 | sum_layout_power = np.sum(layouts_power, axis=0) 129 | mean_power = np.zeros(rows * cols, dtype=np.float32) 130 | for i in range(rows * cols): 131 | if n_copies[i]>0: 132 | mean_power[i] = sum_layout_power[i] / n_copies[i] 133 | for ind in range(rows * cols): 134 | r_i = np.floor(ind / cols) 135 | c_i = np.floor(ind - r_i * cols) 136 | layouts_cr[ind, 0] = c_i 137 | layouts_cr[ind, 1] = r_i 138 | np.savetxt(xfname, layouts_cr, fmt='%d', delimiter=" ") 139 | np.savetxt(yfname, mean_power, fmt='%f', delimiter=" ") 140 | return 141 | 142 | # calculate fitness value of the population 143 | def mc_fitness(self, pop, rows, cols, pop_size, N, lp): 144 | for i in range(pop_size): 145 | print("layout {}...".format(i)) 146 | xy_position = np.zeros((2, N), dtype=np.float32) # x y position 147 | cr_position = np.zeros((2, N), dtype=np.int32) # column row position 148 | ind_position = np.zeros(N, dtype=np.int32) 149 | ind_pos = 0 150 | for ind in range(rows * cols): 151 | if pop[i, ind] == 1: 152 | r_i = np.floor(ind / cols) 153 | c_i = np.floor(ind - r_i * cols) 154 | cr_position[0, ind_pos] = c_i 155 | cr_position[1, ind_pos] = r_i 156 | xy_position[0, ind_pos] = c_i * self.cell_width + self.cell_width_half 157 | xy_position[1, ind_pos] = r_i * self.cell_width + self.cell_width_half 158 | ind_position[ind_pos] = ind 159 | ind_pos += 1 160 | lp_power_accum = np.zeros(N, dtype=np.float32) # a specific layout power accumulate 161 | for ind_t in range(len(self.theta)): 162 | for ind_v in range(len(self.velocity)): 163 | trans_matrix = np.array( 164 | [[np.cos(self.theta[ind_t]), -np.sin(self.theta[ind_t])], 165 | [np.sin(self.theta[ind_t]), np.cos(self.theta[ind_t])]], 166 | np.float32) 167 | trans_xy_position = np.matmul(trans_matrix, xy_position) 168 | speed_deficiency = self.wake_calculate(trans_xy_position, N) 169 | 170 | actual_velocity = (1 - speed_deficiency) * self.velocity[ind_v] 171 | lp_power = self.layout_power(actual_velocity, 172 | N) # total power of a specific layout specific wind speed specific theta 173 | lp_power = lp_power * self.f_theta_v[ind_t, ind_v] 174 | lp_power_accum += lp_power 175 | 176 | lp[i, ind_position] = lp_power_accum 177 | 178 | return 179 | 180 | 181 | # calculate wake effect 182 | def wake_calculate(self, trans_xy_position, N): 183 | sorted_index = np.argsort(-trans_xy_position[1, :]) # y value descending 184 | wake_deficiency = np.zeros(N, dtype=np.float32) 185 | wake_deficiency[sorted_index[0]] = 0 186 | for i in range(1, N): 187 | for j in range(i): 188 | xdis = np.absolute(trans_xy_position[0, sorted_index[i]] - trans_xy_position[0, sorted_index[j]]) 189 | ydis = np.absolute(trans_xy_position[1, sorted_index[i]] - trans_xy_position[1, sorted_index[j]]) 190 | d = self.cal_deficiency(dx=xdis, dy=ydis, r=self.turbine.rator_radius, 191 | ec=self.turbine.entrainment_const) 192 | wake_deficiency[sorted_index[i]] += d ** 2 193 | 194 | wake_deficiency[sorted_index[i]] = np.sqrt(wake_deficiency[sorted_index[i]]) 195 | return wake_deficiency 196 | 197 | # ec : entrainment_const 198 | def cal_deficiency(self, dx, dy, r, ec): 199 | if dy == 0: 200 | return 0 201 | R = r + ec * dy 202 | inter_area = self.cal_interaction_area(dx=dx, dy=dy, r=r, R=R) 203 | d = 2.0 / 3.0 * (r ** 2) / (R ** 2) * inter_area / (np.pi * r ** 2) 204 | return d 205 | 206 | #calculate ineraction area 207 | def cal_interaction_area(self, dx, dy, r, R): 208 | if dx >= r + R: 209 | return 0 210 | elif dx >= np.sqrt(R ** 2 - r ** 2): 211 | alpha = np.arccos((R ** 2 + dx ** 2 - r ** 2) / (2 * R * dx)) 212 | beta = np.arccos((r ** 2 + dx ** 2 - R ** 2) / (2 * r * dx)) 213 | A1 = alpha * R ** 2 214 | A2 = beta * r ** 2 215 | A3 = R * dx * np.sin(alpha) 216 | return A1 + A2 - A3 217 | elif dx >= R - r: 218 | alpha = np.arccos((R ** 2 + dx ** 2 - r ** 2) / (2 * R * dx)) 219 | beta = np.pi - np.arccos((r ** 2 + dx ** 2 - R ** 2) / (2 * r * dx)) 220 | A1 = alpha * R ** 2 221 | A2 = beta * r ** 2 222 | A3 = R * dx * np.sin(alpha) 223 | return np.pi * r ** 2 - (A2 + A3 - A1) 224 | else: 225 | return np.pi * r ** 2 226 | 227 | def layout_power(self, velocity, N): 228 | power = np.zeros(N, dtype=np.float32) 229 | for i in range(N): 230 | power[i] = self.turbine.P_i_X(velocity[i]) 231 | return power 232 | 233 | # conventional genetic algorithm 234 | def conventional_genetic_alg(self, ind_time=0,result_folder=None): # conventional genetic algorithm 235 | P_rate_total = self.cal_P_rate_total() 236 | start_time = datetime.now() 237 | print("conventional genetic algorithm starts....") 238 | fitness_generations = np.zeros(self.iteration, dtype=np.float32) # best fitness value in each generation 239 | best_layout_generations = np.zeros((self.iteration, self.rows * self.cols), 240 | dtype=np.int32) # best layout in each generation 241 | best_layout_NA_generations = np.zeros((self.iteration, self.rows * self.cols), 242 | dtype=np.int32) # best layout in each generation 243 | power_order = np.zeros((self.pop_size, self.N), 244 | dtype=np.int32) # each row is a layout cell indices. in each layout, order turbine power from least to largest 245 | pop = np.copy(self.init_pop) 246 | pop_NA = np.copy(self.init_pop_NA) 247 | pop_indices = np.copy(self.init_pop_nonezero_indices) # each row is a layout cell indices. 248 | 249 | eN = int(np.floor(self.pop_size * self.elite_rate)) # elite number 250 | rN = int(int(np.floor(self.pop_size * self.mutate_rate)) / eN) * eN # reproduce number 251 | mN = rN # mutation number 252 | cN = self.pop_size - eN - mN # crossover number 253 | 254 | for gen in range(self.iteration): 255 | print("generation {}...".format(gen)) 256 | fitness_value = self.conventional_fitness(pop=pop, rows=self.rows, cols=self.cols, pop_size=self.pop_size, 257 | N=self.N, 258 | po=power_order) 259 | sorted_index = np.argsort(-fitness_value) # fitness value descending from largest to least 260 | 261 | pop = pop[sorted_index, :] 262 | pop_NA=pop_NA[sorted_index, :] 263 | 264 | power_order = power_order[sorted_index, :] 265 | pop_indices = pop_indices[sorted_index, :] 266 | 267 | if gen == 0: 268 | fitness_generations[gen] = fitness_value[sorted_index[0]] 269 | best_layout_generations[gen, :] = pop[0, :] 270 | best_layout_NA_generations[gen, :] = pop_NA[0, :] 271 | else: 272 | if fitness_value[sorted_index[0]] > fitness_generations[gen - 1]: 273 | fitness_generations[gen] = fitness_value[sorted_index[0]] 274 | best_layout_generations[gen, :] = pop[0, :] 275 | best_layout_NA_generations[gen, :] = pop_NA[0, :] 276 | else: 277 | fitness_generations[gen] = fitness_generations[gen - 1] 278 | best_layout_generations[gen, :] = best_layout_generations[gen - 1, :] 279 | best_layout_NA_generations[gen, :] = best_layout_NA_generations[gen - 1, :] 280 | n_parents, parent_layouts,parent_layouts_NA, parent_pop_indices = self.conventional_select(pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 281 | pop_size=self.pop_size, 282 | elite_rate=self.elite_rate, 283 | random_rate=self.random_rate) 284 | self.conventional_crossover(N=self.N, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, pop_size=self.pop_size, 285 | n_parents=n_parents, 286 | parent_layouts=parent_layouts,parent_layouts_NA=parent_layouts_NA, parent_pop_indices=parent_pop_indices) 287 | 288 | 289 | self.conventional_mutation(rows=self.rows, cols=self.cols, N=self.N, pop=pop,pop_NA= pop_NA,pop_indices=pop_indices, 290 | pop_size=self.pop_size, 291 | mutation_rate=self.mutate_rate) 292 | end_time = datetime.now() 293 | run_time = (end_time - start_time).total_seconds() 294 | eta_generations = np.copy(fitness_generations) 295 | eta_generations = eta_generations * (1.0 / P_rate_total) 296 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 297 | filename = "{}/conventional_eta_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 298 | np.savetxt(filename, eta_generations, fmt='%f', delimiter=" ") 299 | filename = "{}/conventional_best_layouts_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 300 | np.savetxt(filename, best_layout_generations, fmt='%d', delimiter=" ") 301 | filename = "{}/conventional_best_layouts_NA_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 302 | np.savetxt(filename, best_layout_NA_generations, fmt='%d', delimiter=" ") 303 | print("conventional genetic algorithm ends.") 304 | filename = "{}/conventional_runtime.txt".format(result_folder) 305 | f = open(filename, "a+") 306 | f.write("{}\n".format(run_time)) 307 | f.close() 308 | 309 | filename = "{}/conventional_eta.txt".format(result_folder) 310 | f = open(filename, "a+") 311 | f.write("{}\n".format(eta_generations[self.iteration - 1])) 312 | f.close() 313 | return run_time, eta_generations[self.iteration - 1] 314 | 315 | def conventional_mutation(self, rows, cols, N, pop,pop_NA, pop_indices, pop_size, mutation_rate): 316 | np.random.seed(seed=int(time.time())) 317 | for i in range(pop_size): 318 | if np.random.randn() > mutation_rate: 319 | continue 320 | while True: 321 | turbine_pos = np.random.randint(0, cols * rows) 322 | if pop_NA[i, turbine_pos] == 1: 323 | break 324 | while True: 325 | null_turbine_pos = np.random.randint(0, cols * rows) 326 | if pop_NA[i, null_turbine_pos] == 0: 327 | break 328 | pop[i, turbine_pos] = 0 329 | pop[i, null_turbine_pos] = 1 330 | 331 | pop_NA[i, turbine_pos] = 0 332 | pop_NA[i, null_turbine_pos] = 1 333 | 334 | for j in range(N): 335 | if pop_indices[i, j] == turbine_pos: 336 | pop_indices[i, j] = null_turbine_pos 337 | break 338 | pop_indices[i, :] = np.sort(pop_indices[i, :]) 339 | return 340 | 341 | def conventional_crossover(self, N, pop,pop_NA, pop_indices, pop_size, n_parents, 342 | parent_layouts,parent_layouts_NA, parent_pop_indices): 343 | n_counter = 0 344 | np.random.seed(seed=int(time.time())) # init random seed 345 | while n_counter < pop_size: 346 | male = np.random.randint(0, n_parents) 347 | female = np.random.randint(0, n_parents) 348 | if male != female: 349 | cross_point = np.random.randint(1, N) 350 | if parent_pop_indices[male, cross_point - 1] < parent_pop_indices[female, cross_point]: 351 | pop[n_counter, :] = 0 352 | pop[n_counter, :parent_pop_indices[male, cross_point - 1] + 1] = parent_layouts[male, 353 | :parent_pop_indices[ 354 | male, cross_point - 1] + 1] 355 | pop[n_counter, parent_pop_indices[female, cross_point]:] = parent_layouts[female, 356 | parent_pop_indices[female, cross_point]:] 357 | 358 | pop_NA[n_counter, :] = pop[n_counter, :] 359 | for i in self.NA_loc: 360 | pop_NA[n_counter,i-1]=2 361 | pop_indices[n_counter, :cross_point] = parent_pop_indices[male, :cross_point] 362 | pop_indices[n_counter, cross_point:] = parent_pop_indices[female, cross_point:] 363 | n_counter += 1 364 | return 365 | 366 | def conventional_select(self, pop,pop_NA, pop_indices, pop_size, elite_rate, random_rate): 367 | n_elite = int(pop_size * elite_rate) 368 | parents_ind = [i for i in range(n_elite)] 369 | np.random.seed(seed=int(time.time())) # init random seed 370 | for i in range(n_elite, pop_size): 371 | if np.random.randn() < random_rate: 372 | parents_ind.append(i) 373 | parent_layouts = pop[parents_ind, :] 374 | parent_layouts_NA = pop_NA[parents_ind, :] 375 | parent_pop_indices = pop_indices[parents_ind, :] 376 | return len(parent_pop_indices), parent_layouts,parent_layouts_NA, parent_pop_indices 377 | 378 | def conventional_fitness(self, pop, rows, cols, pop_size, N, po): 379 | fitness_val = np.zeros(pop_size, dtype=np.float32) 380 | for i in range(pop_size): 381 | 382 | # layout = np.reshape(pop[i, :], newshape=(rows, cols)) 383 | xy_position = np.zeros((2, N), dtype=np.float32) # x y position 384 | cr_position = np.zeros((2, N), dtype=np.int32) # column row position 385 | ind_position = np.zeros(N, dtype=np.int32) 386 | ind_pos = 0 387 | for ind in range(rows * cols): 388 | if pop[i, ind] == 1: 389 | r_i = np.floor(ind / cols) 390 | c_i = np.floor(ind - r_i * cols) 391 | cr_position[0, ind_pos] = c_i 392 | cr_position[1, ind_pos] = r_i 393 | xy_position[0, ind_pos] = c_i * self.cell_width + self.cell_width_half 394 | xy_position[1, ind_pos] = r_i * self.cell_width + self.cell_width_half 395 | ind_position[ind_pos] = ind 396 | ind_pos += 1 397 | lp_power_accum = np.zeros(N, dtype=np.float32) # a specific layout power accumulate 398 | for ind_t in range(len(self.theta)): 399 | for ind_v in range(len(self.velocity)): 400 | trans_matrix = np.array( 401 | [[np.cos(self.theta[ind_t]), -np.sin(self.theta[ind_t])], 402 | [np.sin(self.theta[ind_t]), np.cos(self.theta[ind_t])]], 403 | np.float32) 404 | 405 | trans_xy_position = np.matmul(trans_matrix, xy_position) 406 | speed_deficiency = self.wake_calculate(trans_xy_position, N) 407 | 408 | actual_velocity = (1 - speed_deficiency) * self.velocity[ind_v] 409 | lp_power = self.layout_power(actual_velocity, 410 | N) # total power of a specific layout specific wind speed specific theta 411 | lp_power = lp_power * self.f_theta_v[ind_t, ind_v] 412 | lp_power_accum += lp_power 413 | 414 | sorted_index = np.argsort(lp_power_accum) # power from least to largest 415 | po[i, :] = ind_position[sorted_index] 416 | fitness_val[i] = np.sum(lp_power_accum) 417 | return fitness_val 418 | 419 | # AGA: adaptive genetic algorithm 420 | def adaptive_genetic_alg(self, ind_time=0,result_folder=None): # adaptive genetic algorithm 421 | P_rate_total = self.cal_P_rate_total() 422 | start_time = datetime.now() 423 | print("adaptive genetic algorithm starts....") 424 | fitness_generations = np.zeros(self.iteration, dtype=np.float32) # best fitness value in each generation 425 | best_layout_generations = np.zeros((self.iteration, self.rows * self.cols), 426 | dtype=np.int32) # best layout in each generation 427 | best_layout_NA_generations = np.zeros((self.iteration, self.rows * self.cols), 428 | dtype=np.int32) # best layout in each generation 429 | 430 | power_order = np.zeros((self.pop_size, self.N), 431 | dtype=np.int32) # each row is a layout cell indices. in each layout, order turbine power from least to largest 432 | pop = np.copy(self.init_pop) 433 | pop_NA = np.copy(self.init_pop_NA) 434 | pop_indices = np.copy(self.init_pop_nonezero_indices) # each row is a layout cell indices. 435 | 436 | eN = int(np.floor(self.pop_size * self.elite_rate)) # elite number 437 | rN = int(int(np.floor(self.pop_size * self.mutate_rate)) / eN) * eN # reproduce number 438 | mN = rN # mutation number 439 | cN = self.pop_size - eN - mN # crossover number 440 | 441 | for gen in range(self.iteration): 442 | print("generation {}...".format(gen)) 443 | fitness_value = self.adaptive_fitness(pop=pop, rows=self.rows, cols=self.cols, pop_size=self.pop_size, 444 | N=self.N, 445 | po=power_order) 446 | sorted_index = np.argsort(-fitness_value) # fitness value descending from largest to least 447 | 448 | pop = pop[sorted_index, :] 449 | pop_NA = pop_NA[sorted_index, :] 450 | power_order = power_order[sorted_index, :] 451 | pop_indices = pop_indices[sorted_index, :] 452 | if gen == 0: 453 | fitness_generations[gen] = fitness_value[sorted_index[0]] 454 | best_layout_generations[gen, :] = pop[0, :] 455 | best_layout_NA_generations[gen, :] = pop_NA[0, :] 456 | else: 457 | if fitness_value[sorted_index[0]] > fitness_generations[gen - 1]: 458 | fitness_generations[gen] = fitness_value[sorted_index[0]] 459 | best_layout_generations[gen, :] = pop[0, :] 460 | best_layout_NA_generations[gen, :] = pop_NA[0, :] 461 | else: 462 | fitness_generations[gen] = fitness_generations[gen - 1] 463 | best_layout_generations[gen, :] = best_layout_generations[gen - 1, :] 464 | best_layout_NA_generations[gen, :] = best_layout_NA_generations[gen - 1, :] 465 | self.adaptive_move_worst(rows=self.rows, cols=self.cols, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 466 | pop_size=self.pop_size, power_order=power_order) 467 | 468 | 469 | 470 | n_parents, parent_layouts,parent_layouts_NA, parent_pop_indices = self.adaptive_select(pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 471 | pop_size=self.pop_size, 472 | elite_rate=self.elite_rate, 473 | random_rate=self.random_rate) 474 | 475 | 476 | self.adaptive_crossover(N=self.N, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, pop_size=self.pop_size, 477 | n_parents=n_parents, 478 | parent_layouts=parent_layouts,parent_layouts_NA=parent_layouts_NA, parent_pop_indices=parent_pop_indices) 479 | 480 | 481 | self.adaptive_mutation(rows=self.rows, cols=self.cols, N=self.N, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 482 | pop_size=self.pop_size, 483 | mutation_rate=self.mutate_rate) 484 | 485 | 486 | end_time = datetime.now() 487 | run_time = (end_time - start_time).total_seconds() 488 | eta_generations = np.copy(fitness_generations) 489 | eta_generations = eta_generations * (1.0 / P_rate_total) 490 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 491 | 492 | filename = "{}/adaptive_eta_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 493 | np.savetxt(filename, eta_generations, fmt='%f', delimiter=" ") 494 | filename = "{}/adaptive_best_layouts_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 495 | np.savetxt(filename, best_layout_generations, fmt='%d', delimiter=" ") 496 | filename = "{}/adaptive_best_layouts_NA_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 497 | np.savetxt(filename, best_layout_NA_generations, fmt='%d', delimiter=" ") 498 | print("adaptive genetic algorithm ends.") 499 | filename = "{}/adaptive_runtime.txt".format(result_folder) 500 | f = open(filename, "a+") 501 | f.write("{}\n".format(run_time)) 502 | f.close() 503 | 504 | filename = "{}/adaptive_eta.txt".format(result_folder) 505 | f = open(filename, "a+") 506 | f.write("{}\n".format(eta_generations[self.iteration - 1])) 507 | f.close() 508 | 509 | return run_time, eta_generations[self.iteration - 1] 510 | 511 | def adaptive_move_worst(self, rows, cols, pop,pop_NA, pop_indices, pop_size, power_order): 512 | np.random.seed(seed=int(time.time())) 513 | for i in range(pop_size): 514 | turbine_pos = power_order[i, 0] 515 | while True: 516 | null_turbine_pos = np.random.randint(0, cols * rows) 517 | if pop_NA[i, null_turbine_pos] == 0: 518 | break 519 | pop[i, turbine_pos] = 0 520 | pop[i, null_turbine_pos] = 1 521 | pop_NA[i, turbine_pos] = 0 522 | pop_NA[i, null_turbine_pos] = 1 523 | power_order[i, 0] = null_turbine_pos 524 | pop_indices[i, :] = np.sort(power_order[i, :]) 525 | return 526 | 527 | def adaptive_mutation(self, rows, cols, N, pop,pop_NA, pop_indices, pop_size, mutation_rate): 528 | np.random.seed(seed=int(time.time())) 529 | for i in range(pop_size): 530 | if np.random.randn() > mutation_rate: 531 | continue 532 | while True: 533 | turbine_pos = np.random.randint(0, cols * rows) 534 | if pop_NA[i, turbine_pos] == 1: 535 | break 536 | while True: 537 | null_turbine_pos = np.random.randint(0, cols * rows) 538 | if pop_NA[i, null_turbine_pos] == 0: 539 | break 540 | pop[i, turbine_pos] = 0 541 | pop[i, null_turbine_pos] = 1 542 | 543 | pop_NA[i, turbine_pos] = 0 544 | pop_NA[i, null_turbine_pos] = 1 545 | 546 | for j in range(N): 547 | if pop_indices[i, j] == turbine_pos: 548 | pop_indices[i, j] = null_turbine_pos 549 | break 550 | pop_indices[i, :] = np.sort(pop_indices[i, :]) 551 | return 552 | 553 | def adaptive_crossover(self, N, pop,pop_NA, pop_indices, pop_size, n_parents, 554 | parent_layouts,parent_layouts_NA, parent_pop_indices): 555 | n_counter = 0 556 | np.random.seed(seed=int(time.time())) # init random seed 557 | while n_counter < pop_size: 558 | male = np.random.randint(0, n_parents) 559 | female = np.random.randint(0, n_parents) 560 | if male != female: 561 | cross_point = np.random.randint(1, N) 562 | if parent_pop_indices[male, cross_point - 1] < parent_pop_indices[female, cross_point]: 563 | pop[n_counter, :] = 0 564 | pop[n_counter, :parent_pop_indices[male, cross_point - 1] + 1] = parent_layouts[male, 565 | :parent_pop_indices[ 566 | male, cross_point - 1] + 1] 567 | pop[n_counter, parent_pop_indices[female, cross_point]:] = parent_layouts[female, 568 | parent_pop_indices[female, cross_point]:] 569 | 570 | pop_NA[n_counter, :] = pop[n_counter, :] 571 | for i in self.NA_loc: 572 | pop_NA[n_counter, i - 1] = 2 573 | 574 | pop_indices[n_counter, :cross_point] = parent_pop_indices[male, :cross_point] 575 | pop_indices[n_counter, cross_point:] = parent_pop_indices[female, cross_point:] 576 | n_counter += 1 577 | return 578 | 579 | def adaptive_select(self, pop,pop_NA, pop_indices, pop_size, elite_rate, random_rate): 580 | n_elite = int(pop_size * elite_rate) 581 | parents_ind = [i for i in range(n_elite)] 582 | np.random.seed(seed=int(time.time())) # init random seed 583 | for i in range(n_elite, pop_size): 584 | if np.random.randn() < random_rate: 585 | parents_ind.append(i) 586 | parent_layouts = pop[parents_ind, :] 587 | parent_layouts_NA = pop_NA[parents_ind, :] 588 | parent_pop_indices = pop_indices[parents_ind, :] 589 | return len(parent_pop_indices), parent_layouts, parent_layouts_NA, parent_pop_indices 590 | 591 | def adaptive_fitness(self, pop, rows, cols, pop_size, N, po): 592 | fitness_val = np.zeros(pop_size, dtype=np.float32) 593 | for i in range(pop_size): 594 | 595 | # layout = np.reshape(pop[i, :], newshape=(rows, cols)) 596 | xy_position = np.zeros((2, N), dtype=np.float32) # x y position 597 | cr_position = np.zeros((2, N), dtype=np.int32) # column row position 598 | ind_position = np.zeros(N, dtype=np.int32) 599 | ind_pos = 0 600 | for ind in range(rows * cols): 601 | if pop[i, ind] == 1: 602 | r_i = np.floor(ind / cols) 603 | c_i = np.floor(ind - r_i * cols) 604 | cr_position[0, ind_pos] = c_i 605 | cr_position[1, ind_pos] = r_i 606 | xy_position[0, ind_pos] = c_i * self.cell_width + self.cell_width_half 607 | xy_position[1, ind_pos] = r_i * self.cell_width + self.cell_width_half 608 | ind_position[ind_pos] = ind 609 | ind_pos += 1 610 | lp_power_accum = np.zeros(N, dtype=np.float32) # a specific layout power accumulate 611 | for ind_t in range(len(self.theta)): 612 | for ind_v in range(len(self.velocity)): 613 | trans_matrix = np.array( 614 | [[np.cos(self.theta[ind_t]), -np.sin(self.theta[ind_t])], 615 | [np.sin(self.theta[ind_t]), np.cos(self.theta[ind_t])]], 616 | np.float32) 617 | 618 | trans_xy_position = np.matmul(trans_matrix, xy_position) 619 | speed_deficiency = self.wake_calculate(trans_xy_position, N) 620 | 621 | actual_velocity = (1 - speed_deficiency) * self.velocity[ind_v] 622 | lp_power = self.layout_power(actual_velocity, 623 | N) # total power of a specific layout specific wind speed specific theta 624 | lp_power = lp_power * self.f_theta_v[ind_t, ind_v] 625 | lp_power_accum += lp_power 626 | 627 | sorted_index = np.argsort(lp_power_accum) # power from least to largest 628 | po[i, :] = ind_position[sorted_index] 629 | 630 | fitness_val[i] = np.sum(lp_power_accum) 631 | 632 | return fitness_val 633 | 634 | # SUGGA: support vector regression guided genetic algorithm 635 | def sugga_genetic_alg(self, ind_time=0,svr_model=None,result_folder=None): 636 | 637 | P_rate_total = self.cal_P_rate_total() 638 | start_time = datetime.now() 639 | print("Support vector regression guided genetic algorithm starts....") 640 | fitness_generations = np.zeros(self.iteration, dtype=np.float32) # best fitness value in each generation 641 | best_layout_generations = np.zeros((self.iteration, self.rows * self.cols), 642 | dtype=np.int32) # best layout in each generation 643 | best_layout_NA_generations = np.zeros((self.iteration, self.rows * self.cols), 644 | dtype=np.int32) # best layout in each generation 645 | 646 | power_order = np.zeros((self.pop_size, self.N), 647 | dtype=np.int32) # each row is a layout cell indices. in each layout, order turbine power from least to largest 648 | pop = np.copy(self.init_pop) 649 | pop_NA = np.copy(self.init_pop_NA) 650 | pop_indices = np.copy(self.init_pop_nonezero_indices) # each row is a layout cell indices. 651 | 652 | eN = int(np.floor(self.pop_size * self.elite_rate)) # elite number 653 | rN = int(int(np.floor(self.pop_size * self.mutate_rate)) / eN) * eN # reproduce number 654 | mN = rN # mutation number 655 | cN = self.pop_size - eN - mN # crossover number 656 | 657 | for gen in range(self.iteration): 658 | print("generation {}...".format(gen)) 659 | fitness_value = self.sugga_fitness(pop=pop, rows=self.rows, cols=self.cols, pop_size=self.pop_size, 660 | N=self.N, 661 | po=power_order) 662 | sorted_index = np.argsort(-fitness_value) # fitness value descending from largest to least 663 | 664 | pop = pop[sorted_index, :] 665 | pop_NA = pop_NA[sorted_index, :] 666 | power_order = power_order[sorted_index, :] 667 | pop_indices = pop_indices[sorted_index, :] 668 | if gen == 0: 669 | fitness_generations[gen] = fitness_value[sorted_index[0]] 670 | best_layout_generations[gen, :] = pop[0, :] 671 | best_layout_NA_generations[gen, :] = pop_NA[0, :] 672 | else: 673 | if fitness_value[sorted_index[0]] > fitness_generations[gen - 1]: 674 | fitness_generations[gen] = fitness_value[sorted_index[0]] 675 | best_layout_generations[gen, :] = pop[0, :] 676 | best_layout_NA_generations[gen, :] = pop_NA[0, :] 677 | else: 678 | fitness_generations[gen] = fitness_generations[gen - 1] 679 | best_layout_generations[gen, :] = best_layout_generations[gen - 1, :] 680 | best_layout_NA_generations[gen, :] = best_layout_NA_generations[gen - 1, :] 681 | self.sugga_move_worst(rows=self.rows, cols=self.cols, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 682 | pop_size=self.pop_size, power_order=power_order, svr_model=svr_model) 683 | 684 | 685 | 686 | n_parents, parent_layouts,parent_layouts_NA, parent_pop_indices = self.sugga_select(pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 687 | pop_size=self.pop_size, 688 | elite_rate=self.elite_rate, 689 | random_rate=self.random_rate) 690 | 691 | 692 | self.sugga_crossover(N=self.N, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, pop_size=self.pop_size, 693 | n_parents=n_parents, 694 | parent_layouts=parent_layouts,parent_layouts_NA=parent_layouts_NA, parent_pop_indices=parent_pop_indices) 695 | 696 | 697 | 698 | self.sugga_mutation(rows=self.rows, cols=self.cols, N=self.N, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 699 | pop_size=self.pop_size, 700 | mutation_rate=self.mutate_rate) 701 | 702 | end_time = datetime.now() 703 | run_time = (end_time - start_time).total_seconds() 704 | eta_generations = np.copy(fitness_generations) 705 | eta_generations = eta_generations * (1.0 / P_rate_total) 706 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 707 | 708 | filename = "{}/sugga_eta_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 709 | np.savetxt(filename, eta_generations, fmt='%f', delimiter=" ") 710 | filename = "{}/sugga_best_layouts_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 711 | np.savetxt(filename, best_layout_generations, fmt='%d', delimiter=" ") 712 | filename = "{}/sugga_best_layouts_NA_N{}_{}_{}.dat".format(result_folder,self.N, ind_time, time_stamp) 713 | np.savetxt(filename, best_layout_NA_generations, fmt='%d', delimiter=" ") 714 | print("Support vector regression guided genetic algorithm ends.") 715 | filename = "{}/sugga_runtime.txt".format(result_folder) 716 | f = open(filename, "a+") 717 | f.write("{}\n".format(run_time)) 718 | f.close() 719 | filename = "{}/sugga_eta.txt".format(result_folder) 720 | f = open(filename, "a+") 721 | f.write("{}\n".format(eta_generations[self.iteration - 1])) 722 | f.close() 723 | return run_time, eta_generations[self.iteration - 1] 724 | 725 | def sugga_move_worst(self, rows, cols, pop,pop_NA, pop_indices, pop_size, power_order, mars=None,svr_model=None): 726 | np.random.seed(seed=int(time.time())) 727 | for i in range(pop_size): 728 | r = np.random.randn() 729 | if r < 0.5: 730 | self.sugga_move_worst_case_random(i=i, rows=rows, cols=cols, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 731 | pop_size=pop_size, power_order=power_order) 732 | else: 733 | self.sugga_move_worst_case_best(i=i, rows=rows, cols=cols, pop=pop,pop_NA=pop_NA, pop_indices=pop_indices, 734 | pop_size=pop_size, power_order=power_order, mars=mars,svr_model=svr_model) 735 | 736 | return 737 | 738 | def sugga_move_worst_case_random(self, i, rows, cols, pop,pop_NA, pop_indices, pop_size, power_order): 739 | np.random.seed(seed=int(time.time())) 740 | turbine_pos = power_order[i, 0] 741 | while True: 742 | null_turbine_pos = np.random.randint(0, cols * rows) 743 | if pop_NA[i, null_turbine_pos] == 0: 744 | break 745 | pop[i, turbine_pos] = 0 746 | pop[i, null_turbine_pos] = 1 747 | pop_NA[i, turbine_pos] = 0 748 | pop_NA[i, null_turbine_pos] = 1 749 | 750 | power_order[i, 0] = null_turbine_pos 751 | pop_indices[i, :] = np.sort(power_order[i, :]) 752 | return 753 | 754 | def sugga_move_worst_case_best(self, i, rows, cols, pop,pop_NA, pop_indices, pop_size, power_order, mars,svr_model): 755 | np.random.seed(seed=int(time.time())) 756 | n_candiate = 5 757 | pos_candidate = np.zeros((n_candiate, 2), dtype=np.int32) 758 | ind_pos_candidate = np.zeros(n_candiate, dtype=np.int32) 759 | turbine_pos = power_order[i, 0] 760 | ind_can = 0 761 | while True: 762 | null_turbine_pos = np.random.randint(0, cols * rows) 763 | if pop_NA[i, null_turbine_pos] == 0: 764 | pos_candidate[ind_can, 1] = int(np.floor(null_turbine_pos / cols)) 765 | pos_candidate[ind_can, 0] = int(np.floor(null_turbine_pos - pos_candidate[ind_can, 1] * cols)) 766 | ind_pos_candidate[ind_can] = null_turbine_pos 767 | ind_can += 1 768 | if ind_can == n_candiate: 769 | break 770 | svr_val = svr_model.predict(pos_candidate) 771 | sorted_index = np.argsort(-svr_val) # fitness value descending from largest to least 772 | null_turbine_pos = ind_pos_candidate[sorted_index[0]] 773 | 774 | pop[i, turbine_pos] = 0 775 | pop[i, null_turbine_pos] = 1 776 | 777 | pop_NA[i, turbine_pos] = 0 778 | pop_NA[i, null_turbine_pos] = 1 779 | 780 | power_order[i, 0] = null_turbine_pos 781 | pop_indices[i, :] = np.sort(power_order[i, :]) 782 | return 783 | 784 | def sugga_move_worst_case_worst(self, i, rows, cols, pop, pop_indices, pop_size, power_order, mars): 785 | np.random.seed(seed=int(time.time())) 786 | n_candiate = 11 787 | pos_candidate = np.zeros((n_candiate, 2), dtype=np.int32) 788 | ind_pos_candidate = np.zeros(n_candiate, dtype=np.int32) 789 | turbine_pos = power_order[i, 0] 790 | ind_can = 0 791 | while True: 792 | null_turbine_pos = np.random.randint(0, cols * rows) 793 | if pop[i, null_turbine_pos] == 0: 794 | pos_candidate[ind_can, 1] = int(np.floor(null_turbine_pos / cols)) 795 | pos_candidate[ind_can, 0] = int(np.floor(null_turbine_pos - pos_candidate[ind_can, 1] * cols)) 796 | ind_pos_candidate[ind_can] = null_turbine_pos 797 | ind_can += 1 798 | if ind_can == n_candiate: 799 | break 800 | mars_val = mars.predict(pos_candidate) 801 | mars_val = mars_val[:, 0] 802 | sorted_index = np.argsort(mars_val) # fitness value descending from least to largest 803 | null_turbine_pos = ind_pos_candidate[sorted_index[0]] 804 | pop[i, turbine_pos] = 0 805 | pop[i, null_turbine_pos] = 1 806 | power_order[i, 0] = null_turbine_pos 807 | pop_indices[i, :] = np.sort(power_order[i, :]) 808 | return 809 | # SUGGA move worst 810 | def sugga_move_worst_case_middle(self, i, rows, cols, pop, pop_indices, pop_size, power_order, mars): 811 | np.random.seed(seed=int(time.time())) 812 | n_candiate = 11 813 | pos_candidate = np.zeros((n_candiate, 2), dtype=np.int32) 814 | ind_pos_candidate = np.zeros(n_candiate, dtype=np.int32) 815 | turbine_pos = power_order[i, 0] 816 | ind_can = 0 817 | while True: 818 | null_turbine_pos = np.random.randint(0, cols * rows) 819 | if pop[i, null_turbine_pos] == 0: 820 | pos_candidate[ind_can, 1] = int(np.floor(null_turbine_pos / cols)) 821 | pos_candidate[ind_can, 0] = int(np.floor(null_turbine_pos - pos_candidate[ind_can, 1] * cols)) 822 | ind_pos_candidate[ind_can] = null_turbine_pos 823 | ind_can += 1 824 | if ind_can == n_candiate: 825 | break 826 | mars_val = mars.predict(pos_candidate) 827 | mars_val = mars_val[:, 0] 828 | sorted_index = np.argsort(-mars_val) # fitness value descending from largest to least 829 | null_turbine_pos = ind_pos_candidate[sorted_index[5]] 830 | pop[i, turbine_pos] = 0 831 | pop[i, null_turbine_pos] = 1 832 | power_order[i, 0] = null_turbine_pos 833 | pop_indices[i, :] = np.sort(power_order[i, :]) 834 | return 835 | # SUGGA mutation 836 | def sugga_mutation(self, rows, cols, N, pop,pop_NA, pop_indices, pop_size, mutation_rate): 837 | np.random.seed(seed=int(time.time())) 838 | for i in range(pop_size): 839 | if np.random.randn() > mutation_rate: 840 | continue 841 | while True: 842 | turbine_pos = np.random.randint(0, cols * rows) 843 | if pop_NA[i, turbine_pos] == 1: 844 | break 845 | while True: 846 | null_turbine_pos = np.random.randint(0, cols * rows) 847 | if pop_NA[i, null_turbine_pos] == 0: 848 | break 849 | pop[i, turbine_pos] = 0 850 | pop[i, null_turbine_pos] = 1 851 | 852 | pop_NA[i, turbine_pos] = 0 853 | pop_NA[i, null_turbine_pos] = 1 854 | 855 | for j in range(N): 856 | if pop_indices[i, j] == turbine_pos: 857 | pop_indices[i, j] = null_turbine_pos 858 | break 859 | pop_indices[i, :] = np.sort(pop_indices[i, :]) 860 | return 861 | 862 | # SUGGA crossover 863 | def sugga_crossover(self, N, pop,pop_NA, pop_indices, pop_size, n_parents, 864 | parent_layouts,parent_layouts_NA, parent_pop_indices): 865 | n_counter = 0 866 | np.random.seed(seed=int(time.time())) # init random seed 867 | while n_counter < pop_size: 868 | male = np.random.randint(0, n_parents) 869 | female = np.random.randint(0, n_parents) 870 | if male != female: 871 | cross_point = np.random.randint(1, N) 872 | if parent_pop_indices[male, cross_point - 1] < parent_pop_indices[female, cross_point]: 873 | pop[n_counter, :] = 0 874 | pop[n_counter, :parent_pop_indices[male, cross_point - 1] + 1] = parent_layouts[male, 875 | :parent_pop_indices[ 876 | male, cross_point - 1] + 1] 877 | pop[n_counter, parent_pop_indices[female, cross_point]:] = parent_layouts[female, 878 | parent_pop_indices[female, cross_point]:] 879 | 880 | pop_NA[n_counter, :] = pop[n_counter, :] 881 | for i in self.NA_loc: 882 | pop_NA[n_counter, i - 1] = 2 883 | 884 | pop_indices[n_counter, :cross_point] = parent_pop_indices[male, :cross_point] 885 | pop_indices[n_counter, cross_point:] = parent_pop_indices[female, cross_point:] 886 | n_counter += 1 887 | return 888 | # SUGGA select 889 | def sugga_select(self, pop,pop_NA, pop_indices, pop_size, elite_rate, random_rate): 890 | n_elite = int(pop_size * elite_rate) 891 | parents_ind = [i for i in range(n_elite)] 892 | np.random.seed(seed=int(time.time())) # init random seed 893 | for i in range(n_elite, pop_size): 894 | if np.random.randn() < random_rate: 895 | parents_ind.append(i) 896 | parent_layouts = pop[parents_ind, :] 897 | parent_layouts_NA = pop_NA[parents_ind, :] 898 | parent_pop_indices = pop_indices[parents_ind, :] 899 | return len(parent_pop_indices), parent_layouts, parent_layouts_NA, parent_pop_indices 900 | #calculate fitness value 901 | def sugga_fitness(self, pop, rows, cols, pop_size, N, po): 902 | fitness_val = np.zeros(pop_size, dtype=np.float32) 903 | for i in range(pop_size): 904 | 905 | # layout = np.reshape(pop[i, :], newshape=(rows, cols)) 906 | xy_position = np.zeros((2, N), dtype=np.float32) # x y position 907 | cr_position = np.zeros((2, N), dtype=np.int32) # column row position 908 | ind_position = np.zeros(N, dtype=np.int32) 909 | ind_pos = 0 910 | for ind in range(rows * cols): 911 | if pop[i, ind] == 1: 912 | r_i = np.floor(ind / cols) 913 | c_i = np.floor(ind - r_i * cols) 914 | cr_position[0, ind_pos] = c_i 915 | cr_position[1, ind_pos] = r_i 916 | xy_position[0, ind_pos] = c_i * self.cell_width + self.cell_width_half 917 | xy_position[1, ind_pos] = r_i * self.cell_width + self.cell_width_half 918 | ind_position[ind_pos] = ind 919 | ind_pos += 1 920 | lp_power_accum = np.zeros(N, dtype=np.float32) # a specific layout power accumulate 921 | for ind_t in range(len(self.theta)): 922 | for ind_v in range(len(self.velocity)): 923 | # print(theta[ind_t]) 924 | # print(np.cos(theta[ind_t])) 925 | trans_matrix = np.array( 926 | [[np.cos(self.theta[ind_t]), -np.sin(self.theta[ind_t])], 927 | [np.sin(self.theta[ind_t]), np.cos(self.theta[ind_t])]], 928 | np.float32) 929 | 930 | trans_xy_position = np.matmul(trans_matrix, xy_position) 931 | speed_deficiency = self.wake_calculate(trans_xy_position, N) 932 | 933 | actual_velocity = (1 - speed_deficiency) * self.velocity[ind_v] 934 | lp_power = self.layout_power(actual_velocity, 935 | N) # total power of a specific layout specific wind speed specific theta 936 | lp_power = lp_power * self.f_theta_v[ind_t, ind_v] 937 | lp_power_accum += lp_power 938 | 939 | sorted_index = np.argsort(lp_power_accum) # power from least to largest 940 | po[i, :] = ind_position[sorted_index] 941 | 942 | fitness_val[i] = np.sum(lp_power_accum) 943 | # 944 | return fitness_val 945 | 946 | 947 | 948 | 949 | class GE_1_5_sleTurbine: 950 | hub_height = 80.0 # unit (m) 951 | rator_diameter = 77.0 # unit m 952 | surface_roughness = 0.25 * 0.001 # unit mm surface roughness 953 | # surface_roughness = 0.25 # unit mm surface roughness 954 | rator_radius = 0 955 | 956 | entrainment_const = 0 957 | 958 | def __init__(self): 959 | self.rator_radius = self.rator_diameter / 2 960 | self.entrainment_const = 0.5 / np.log(self.hub_height / self.surface_roughness) 961 | return 962 | 963 | # power curve 964 | def P_i_X(self, v): 965 | if v < 2.0: 966 | return 0 967 | elif v < 12.8: 968 | return 0.3 * v ** 3 969 | elif v < 18: 970 | return 629.1 971 | else: 972 | return 0 973 | 974 | 975 | 976 | 977 | class LayoutGridMCGenerator: 978 | def __init__(self): 979 | return 980 | 981 | # rows : number of rows in wind farm 982 | # cols : number of columns in wind farm 983 | # n : number of layouts 984 | # N : number of turbines 985 | def gen_mc_grid(rows, cols, n, N, lofname): # , xfname): generate monte carlo wind farm layout grids 986 | np.random.seed(seed=int(time.time())) # init random seed 987 | layouts = np.zeros((n, rows * cols), dtype=np.int32) # one row is a layout 988 | # layouts_cr = np.zeros((n*, 2), dtype=np.float32) # layouts column row index 989 | positionX = np.random.randint(0, cols, size=(N * n * 2)) 990 | positionY = np.random.randint(0, rows, size=(N * n * 2)) 991 | ind_rows = 0 # index of layouts from 0 to n-1 992 | ind_pos = 0 # index of positionX, positionY from 0 to N*n*2-1 993 | # ind_crs = 0 994 | while ind_rows < n: 995 | layouts[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] = 1 996 | if np.sum(layouts[ind_rows, :]) == N: 997 | # for ind in range(rows * cols): 998 | # if layouts[ind_rows, ind] == 1: 999 | # r_i = np.floor(ind / cols) 1000 | # c_i = np.floor(ind - r_i * cols) 1001 | # layouts_cr[ind_crs, 0] = c_i 1002 | # layouts_cr[ind_crs, 1] = r_i 1003 | # ind_crs += 1 1004 | ind_rows += 1 1005 | ind_pos += 1 1006 | if ind_pos >= N * n * 2: 1007 | print("Not enough positions") 1008 | break 1009 | # filename = "positions{}by{}by{}N{}.dat".format(rows, cols, n, N) 1010 | np.savetxt(lofname, layouts, fmt='%d', delimiter=" ") 1011 | # np.savetxt(xfname, layouts_cr, fmt='%d', delimiter=" ") 1012 | return layouts 1013 | 1014 | # rows : number of rows in wind farm 1015 | # cols : number of columns in wind farm 1016 | # n : number of layouts 1017 | # N : number of turbines 1018 | # NA_loc : not usable locations 1019 | # generate layouts with not usable locations 1020 | def gen_mc_grid_with_NA_loc(rows, cols, n, N,NA_loc, lofname,loNAfname): # , xfname): generate monte carlo wind farm layout grids 1021 | np.random.seed(seed=int(time.time())) # init random seed 1022 | layouts = np.zeros((n, rows * cols), dtype=np.int32) # one row is a layout, NA loc is 0 1023 | 1024 | layouts_NA= np.zeros((n, rows * cols), dtype=np.int32) # one row is a layout, NA loc is 2 1025 | for i in NA_loc: 1026 | layouts_NA[:,i-1]=2 1027 | 1028 | # layouts_cr = np.zeros((n*, 2), dtype=np.float32) # layouts column row index 1029 | positionX = np.random.randint(0, cols, size=(N * n * 2)) 1030 | positionY = np.random.randint(0, rows, size=(N * n * 2)) 1031 | ind_rows = 0 # index of layouts from 0 to n-1 1032 | ind_pos = 0 # index of positionX, positionY from 0 to N*n*2-1 1033 | # ind_crs = 0 1034 | N_count=0 1035 | while ind_rows < n: 1036 | cur_state=layouts_NA[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] 1037 | if cur_state!=1 and cur_state!=2: 1038 | layouts[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols]=1 1039 | layouts_NA[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] = 1 1040 | N_count+=1 1041 | if np.sum(layouts[ind_rows, :]) == N: 1042 | ind_rows += 1 1043 | N_count=0 1044 | ind_pos += 1 1045 | if ind_pos >= N * n * 2: 1046 | print("Not enough positions") 1047 | break 1048 | # filename = "positions{}by{}by{}N{}.dat".format(rows, cols, n, N) 1049 | np.savetxt(lofname, layouts, fmt='%d', delimiter=" ") 1050 | np.savetxt(loNAfname, layouts_NA, fmt='%d', delimiter=" ") 1051 | # np.savetxt(xfname, layouts_cr, fmt='%d', delimiter=" ") 1052 | return layouts,layouts_NA 1053 | 1054 | # generate population 1055 | def gen_pop(rows, cols, n, 1056 | N): # generate population very similar to gen_mc_grid, just without saving layouts to a file 1057 | np.random.seed(seed=int(time.time())) 1058 | layouts = np.zeros((n, rows * cols), dtype=np.int32) 1059 | positionX = np.random.randint(0, cols, size=(N * n * 2)) 1060 | positionY = np.random.randint(0, rows, size=(N * n * 2)) 1061 | ind_rows = 0 1062 | ind_pos = 0 1063 | 1064 | while ind_rows < n: 1065 | layouts[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] = 1 1066 | if np.sum(layouts[ind_rows, :]) == N: 1067 | ind_rows += 1 1068 | ind_pos += 1 1069 | if ind_pos >= N * n * 2: 1070 | print("Not enough positions") 1071 | break 1072 | return layouts 1073 | 1074 | # rows : number of rows in wind farm 1075 | # cols : number of columns in wind farm 1076 | # n : number of layouts 1077 | # N : number of turbines 1078 | # NA_loc : not usable locations 1079 | # generate layouts with not usable locations 1080 | def gen_pop_with_NA_loc(rows, cols, n, N, NA_loc): 1081 | np.random.seed(seed=int(time.time())) # init random seed 1082 | layouts = np.zeros((n, rows * cols), dtype=np.int32) # one row is a layout, NA loc is 0 1083 | 1084 | layouts_NA = np.zeros((n, rows * cols), dtype=np.int32) # one row is a layout, NA loc is 2 1085 | for i in NA_loc: 1086 | layouts_NA[:, i - 1] = 2 1087 | 1088 | # layouts_cr = np.zeros((n*, 2), dtype=np.float32) # layouts column row index 1089 | positionX = np.random.randint(0, cols, size=(N * n * 2)) 1090 | positionY = np.random.randint(0, rows, size=(N * n * 2)) 1091 | ind_rows = 0 # index of layouts from 0 to n-1 1092 | ind_pos = 0 # index of positionX, positionY from 0 to N*n*2-1 1093 | # ind_crs = 0 1094 | N_count = 0 1095 | while ind_rows < n: 1096 | cur_state = layouts_NA[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] 1097 | if cur_state != 1 and cur_state != 2: 1098 | layouts[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] = 1 1099 | layouts_NA[ind_rows, positionX[ind_pos] + positionY[ind_pos] * cols] = 1 1100 | N_count += 1 1101 | if np.sum(layouts[ind_rows, :]) == N: 1102 | ind_rows += 1 1103 | N_count = 0 1104 | ind_pos += 1 1105 | if ind_pos >= N * n * 2: 1106 | print("Not enough positions") 1107 | break 1108 | return layouts, layouts_NA 1109 | 1110 | 1111 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import WindFarmGenetic # wind farm layout optimization using genetic algorithms classes 4 | from datetime import datetime 5 | import os 6 | from sklearn.svm import SVR 7 | import pickle 8 | 9 | # Wind farm settings and algorithm settings 10 | # parameters for the genetic algorithm 11 | elite_rate = 0.2 12 | cross_rate = 0.6 13 | random_rate = 0.5 14 | mutate_rate = 0.1 15 | 16 | wt_N = 25 # number of wind turbines 15, 20, or 25 17 | # NA_loc_array : not available location array, the index starting from 1 18 | 19 | # L1 : wind farm, cells 121(inclusive) to 144(inclusive) 20 | NA_loc_array = np.arange(121, 145, 1) 21 | # 22 | # # L2 23 | # NA_loc_array = np.arange(61, 85, 1) 24 | # 25 | # # L3 26 | # NA_loc_array = np.concatenate((np.arange(11, 144, 12), np.arange(12, 145, 12))) 27 | # 28 | # # L4 29 | # NA_loc_array = np.concatenate((np.arange(6, 144, 12), np.arange(7, 145, 12))) 30 | # 31 | # # L5 32 | # NA_loc_array = np.concatenate((np.arange(41, 105, 12), np.arange(42, 105, 12), 33 | # np.arange(43, 105, 12), 34 | # np.arange(44, 105, 12))) 35 | # 36 | # # L6 37 | # NA_loc_array = np.concatenate((np.arange(1, 28, 12), np.arange(2, 28, 12), 38 | # np.arange(12, 37, 12), 39 | # np.arange(11, 37, 12), 40 | # np.arange(109, 145, 12), np.arange(119, 145, 12), 41 | # np.arange(110, 145, 12), 42 | # np.arange(120, 145, 12), 43 | # )) 44 | # 45 | # # L7 46 | # NA_loc_array = np.arange(133, 145, 1) 47 | # 48 | # # L8 49 | # NA_loc_array = np.arange(61, 73, 1) 50 | # 51 | # # L9 52 | # NA_loc_array = np.arange(12, 145, 12) 53 | # 54 | # # L10 55 | # NA_loc_array = np.arange(6, 145, 12) 56 | # 57 | # # L11 58 | # NA_loc_array = np.concatenate((np.arange(42, 105, 12), 59 | # np.arange(43, 105, 12))) 60 | # 61 | # # L12 62 | # NA_loc_array = np.array((1, 2, 11, 12, 13, 24, 121, 132, 133, 134, 143, 144)) 63 | 64 | # convert numpy array to list, datatype convert 65 | NA_loc = NA_loc_array.tolist() 66 | 67 | # L0 68 | # NA_loc = [] 69 | 70 | 71 | population_size = 120 # how many layouts in a population 72 | iteration_times = 200 # how many iterations in a genetic algorithm run 73 | 74 | n_inits = 100 # number of initial populations n_inits >= run_times 75 | run_times = 100 # number of different initial populations 76 | 77 | # wind farm size, cells 78 | cols_cells = 12 # number of cells each row 79 | rows_cells = 12 # number of cells each column 80 | cell_width = 77.0 * 3 # unit : m 81 | 82 | # all data will be save in data folder 83 | data_folder = "data" 84 | if not os.path.exists(data_folder): 85 | os.makedirs(data_folder) 86 | 87 | # Create a WindFarmGenetic object 88 | # create an WindFarmGenetic object. Specify the number of rows and the number columns of the wind farm land. N is the number of wind turbines. 89 | # NA_loc is the not available locations on the wind farm land. Landowners does not want to participate in the wind farm. 90 | # pop_size: how many individuals in the population 91 | # iteration: iteration times of the genetic algorithm 92 | wfg = WindFarmGenetic.WindFarmGenetic(rows=rows_cells, cols=cols_cells, N=wt_N, NA_loc=NA_loc, pop_size=population_size, 93 | iteration=iteration_times, cell_width=cell_width, elite_rate=elite_rate, 94 | cross_rate=cross_rate, random_rate=random_rate, mutate_rate=mutate_rate) 95 | # Specify the wind distribution 96 | # wind distribution is discrete (number of wind speeds) by (number of wind directions) 97 | # wfg.init_1_direction_1_N_speed_13() 98 | # file name to store the wind power distribution SVR model 99 | # svr_model_filename = 'svr_1s1d_N_13.svr' 100 | 101 | # wfg.init_4_direction_1_speed_13() 102 | # svr_model_filename = 'svr_1s4d_13.svr' 103 | 104 | wfg.init_6_direction_1_speed_13() 105 | # svr_model_filename = 'svr_1s6d_13.svr' 106 | 107 | ################################################ 108 | # generate initial populations 109 | ################################################ 110 | # initial population saved folder 111 | init_pops_data_folder = "{}/init_data".format(data_folder) 112 | if not os.path.exists(init_pops_data_folder): 113 | os.makedirs(init_pops_data_folder) 114 | # generate initial populations to start with and store them 115 | # in order to start from the same initial population for different methods 116 | # so it is fair to compare the final results 117 | 118 | for i in range(n_inits): 119 | wfg.gen_init_pop_NA() 120 | wfg.save_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder, i), 121 | "{}/init_{}_NA.dat".format(init_pops_data_folder, i)) 122 | 123 | # Create results folder 124 | # results folder 125 | # adaptive_best_layouts_N60_9_20190422213718.dat : best layout for AGA of run index 9 126 | # result_CGA_20190422213715.dat : run time and best eta for CGA method 127 | results_data_folder = "data/results" 128 | if not os.path.exists(results_data_folder): 129 | os.makedirs(results_data_folder) 130 | # if cg,ag,sg folder does not exist, create these folders. Folders to store the running results 131 | # cg: convertional genetic algorithm 132 | # ag: adaptive genetic algorithm 133 | # sg: support vector regression guided genetic algorithm 134 | cg_result_folder = "{}/cg".format(results_data_folder) 135 | if not os.path.exists(cg_result_folder): 136 | os.makedirs(cg_result_folder) 137 | 138 | ag_result_folder = "{}/ag".format(results_data_folder) 139 | if not os.path.exists(ag_result_folder): 140 | os.makedirs(ag_result_folder) 141 | 142 | sg_result_folder = "{}/sg".format(results_data_folder) 143 | if not os.path.exists(sg_result_folder): 144 | os.makedirs(sg_result_folder) 145 | # resul_arr: run_times by 2 , the first column is the run time in seconds for each run and the second column is the conversion efficiency for the run 146 | result_arr = np.zeros((run_times, 2), dtype=np.float32) 147 | 148 | # Run adaptive genetic algorithm (AGA) 149 | # CGA: Conventional genetic algorithm 150 | for i in range(0, run_times): # run times 151 | print("run times {} ...".format(i)) 152 | # load initial population 153 | wfg.load_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder, i), 154 | "{}/init_{}_NA.dat".format(init_pops_data_folder, i)) 155 | # run the conventional genetic algorithm and return run time and conversion efficiency 156 | run_time, eta = wfg.conventional_genetic_alg(i, result_folder=cg_result_folder) 157 | result_arr[i, 0] = run_time 158 | result_arr[i, 1] = eta 159 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 160 | # save the run time and etas to a file 161 | filename = "{}/result_conventional_{}.dat".format(cg_result_folder, time_stamp) 162 | np.savetxt(filename, result_arr, fmt='%f', delimiter=" ") 163 | 164 | # Run adaptive genetic algorithm (AGA) 165 | # AGA: adaptive genetic algorithm 166 | for i in range(0, run_times): # run times 167 | print("run times {} ...".format(i)) 168 | wfg.load_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder, i), 169 | "{}/init_{}_NA.dat".format(init_pops_data_folder, i)) 170 | run_time, eta = wfg.adaptive_genetic_alg(i, result_folder=ag_result_folder) 171 | result_arr[i, 0] = run_time 172 | result_arr[i, 1] = eta 173 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 174 | filename = "{}/result_adaptive_{}.dat".format(ag_result_folder, time_stamp) 175 | np.savetxt(filename, result_arr, fmt='%f', delimiter=" ") 176 | 177 | # Run support vector regression guided genetic algorithm (SUGGA) 178 | # Generate wind distribution surface 179 | 180 | ############################################# 181 | # generate wind distribution surface 182 | ############################################# 183 | n_mc_samples = 10000 # svr train data, number of layouts to average 184 | 185 | wds_data_folder = "{}/wds".format(data_folder) 186 | if not os.path.exists(wds_data_folder): 187 | os.makedirs(wds_data_folder) 188 | # mc : monte-carlo 189 | 190 | # number of layouts to generate as the training data for regression 191 | # to build the power distribution surface 192 | 193 | # mc_layout.dat file stores layouts only with 0s and 1s. 0 means no turbine here. 1 means one turbine here. 194 | # mc_layout_NA.dat file stores layouts with 0s, 1s and 2s. 2 means no turbine and not available for turbine. 195 | # These two files are used to generate wind power distribution. 196 | # Each file has 10000 lines. Each line is layout. 197 | # gen_mc_grid_with_NA_loc function generates these two files. 198 | train_mc_layouts, train_mc_layouts_NA = WindFarmGenetic.LayoutGridMCGenerator.gen_mc_grid_with_NA_loc(rows_cells, 199 | cols_cells, 200 | n_mc_samples, 201 | wt_N, NA_loc, 202 | "{}/mc_layout.dat".format( 203 | wds_data_folder), 204 | "{}/mc_layout_NA.dat".format( 205 | wds_data_folder)) 206 | 207 | # wfg.init_1_direction_1_N_speed_13() 208 | # file name to store the wind power distribution SVR model 209 | # svr_model_filename = 'svr_1s1d_N_13.svr' 210 | 211 | # wfg.init_4_direction_1_speed_13() 212 | # svr_model_filename = 'svr_1s4d_13.svr' 213 | 214 | # wfg.init_6_direction_1_speed_13() 215 | svr_model_filename = 'svr_1s6d_13.svr' 216 | 217 | # load Monte-Carlo layouts from a text file. 10000 random layouts 218 | layouts = np.genfromtxt("{}/mc_layout.dat".format(wds_data_folder), delimiter=" ", dtype=np.int32) 219 | # generate the location index coordinate and average power output at each location index coordinate 220 | # location index coordinate : in the cells, the cell with index 1 has location index (0,0) and the cell 2 has (1,0) 221 | # store the location index coordinate in x.dat and average power in y.dat 222 | wfg.mc_gen_xy_NA(rows=rows_cells, cols=cols_cells, layouts=layouts, n=n_mc_samples, N=wt_N, 223 | xfname="{}/x.dat".format(wds_data_folder), 224 | yfname="{}/y.dat".format(wds_data_folder)) 225 | 226 | # read index location coordinates 227 | x_original = pd.read_csv("{}/x.dat".format(wds_data_folder), header=None, nrows=rows_cells * cols_cells, 228 | delim_whitespace=True, dtype=np.float32) 229 | x_original = x_original.values 230 | 231 | # read the power output of each index location coordinate 232 | y_original = pd.read_csv("{}/y.dat".format(wds_data_folder), header=None, nrows=rows_cells * cols_cells, 233 | delim_whitespace=True, dtype=np.float32) 234 | y_original = y_original.values.flatten() 235 | 236 | # create a SVR object and specify the kernal and other parameters 237 | svr_model = SVR(kernel='rbf', C=2000.0, gamma=0.3, epsilon=.1) 238 | # build the SVR power distribution model 239 | svr_model.fit(x_original, y_original) 240 | 241 | # save the SVR model to a file 242 | pickle.dump(svr_model, open("{}/{}".format(wds_data_folder, svr_model_filename), 'wb')) 243 | 244 | # This is how to load SVR model from a file 245 | # svr_model = pickle.load(open("{}/{}".format(wds_data_folder,svr_model_filename), 'rb')) 246 | 247 | 248 | # SUGGA: support vector regression guided genetic algorithm 249 | for i in range(0, run_times): # run times 250 | print("run times {} ...".format(i)) 251 | wfg.load_init_pop_NA("{}/init_{}.dat".format(init_pops_data_folder, i), 252 | "{}/init_{}_NA.dat".format(init_pops_data_folder, i)) 253 | run_time, eta = wfg.sugga_genetic_alg(i, svr_model=svr_model, result_folder=sg_result_folder) 254 | result_arr[i, 0] = run_time 255 | result_arr[i, 1] = eta 256 | time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") 257 | filename = "{}/result_sugga_{}.dat".format(sg_result_folder, time_stamp) 258 | np.savetxt(filename, result_arr, fmt='%f', delimiter=" ") 259 | --------------------------------------------------------------------------------