├── .gitignore ├── README.md ├── example_test.py ├── ga.py ├── images ├── Algorithm_iteration.png ├── formula.png └── output.png └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.svg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :bird:**基于罚函数的遗传算法(Penalty-based Genetic Algorithm)** 2 | 3 | ### :arrow_forward:**1.前言** 4 | 5 | 本项目实现了能够添加各种约束条件(等式约束、不等式约束)下的规划模型遗传算法求解(基于罚函数),同时可适用于连续变量、整型变量和01变量。 6 | 7 | ### :bookmark:**2.使用方法** 8 | 9 | 1. 使用git clone https://github.com/leost123456/Genetic-Algorithm.git 克隆到本地仓库,或者直接下载源码。 10 | 2. 进入目录终端输入指令pip install -r requirements.txt安装所需依赖。 11 | 3. 利用该遗传算法代码库求解设置的规划模型即可。 12 | 13 | ### :blue_book:**3.案例分析** 14 | 15 | 假设有如下的目标规划模型: 16 | 17 | ![公式](./images/formula.png) 18 | 19 | 我们即可通过如下代码进行求解(代码中参数包括详细注释): 20 | 21 | ```python 22 | import numpy as np 23 | from GA import GA 24 | 25 | #设置目标哈数 26 | def f(X): 27 | return X[0]**2+X[1]**2+8 28 | 29 | #设置不等式约束条件(默认都是要小于等于0) 30 | def ineq_constraints(X): 31 | #约束1 32 | cons1=X[1]-X[0]**2 33 | return [cons1] 34 | 35 | #设置等式约束(默认都是要小于等于0) 36 | def eq_constraints(X): 37 | #约束2 38 | cons2=-X[0]-X[1]**2+2 39 | return [cons2] #组合成序列的形式 40 | 41 | if __name__ == '__main__': 42 | varbound = np.array([[0,100], [0, 100]]) # 变量值域范围 43 | #‘real’表示连续变量,‘int’表表示整型变量,如果01变量也是选择'int',变量值域限制为01即可 44 | vartype = np.array([['real'],['real']]) # 连续变量类型 45 | 46 | #默认是最小化目标函数 47 | model = GA(function=f, #目标函数 48 | dimension=2, #决策变量数量 49 | variable_type=vartype, #变量类型(序列形式,real,int) 50 | variable_boundaries=varbound, #各变量值域 51 | eq_constraints=eq_constraints, #等式约束函数 52 | ineq_constraints=ineq_constraints, #不等式约束函数 53 | function_timeout=10, #延时(函数没响应) 54 | eq_cons_coefficient=0.001, #等式约束系数(值越小,等式约束越严格) 55 | max_num_iteration=300, #最大迭代次数 56 | population_size=1000, #个体数量(初始解的数量) 57 | penalty_factor=1, #惩罚因子(用于约束条件),越大约束作用越大(要选择合适的值1比较合适) 58 | mutation_probability=0.1, #变异概率 59 | elit_ratio=0.01, #精英选择的比例(每轮都保留一定数量的较优目标函数的个体) 60 | crossover_probability=0.5, #交叉概率 61 | parents_portion=0.3, #父代比例(用于每代的交叉和变异) 62 | crossover_type='uniform', #交叉类型(”one_point“ ”two_point“ "uniform") 63 | max_iteration_without_improv=None, #多少轮没更新后退出 64 | convergence_curve=True, #是否绘制算法迭代收敛曲线 65 | progress_bar=True, #进度条显示 66 | plot_path='.' #保存收敛曲线svg图像的路径 67 | ) 68 | model.run() 69 | #输出信息 70 | best_variable=model.best_variable #最优解 71 | best_function=model.best_function #最优目标函数 72 | report=model.report #每轮的最优目标函数 73 | ``` 74 | 75 | 最终的遗传算法迭代结果如下所示: 76 | 77 | ![算法迭代图](./images/Algorithm_iteration.png) 78 | 79 | 求解得到的最优解如下所示: 80 | 81 | ![最优解](./images/output.png) 82 | 83 | :key:**声明** 84 | 85 | 本项目基于rmsolgi的geneticalgorithm项目进行改进,从而实现各种约束条件下的规划模型求解(加入罚函数),原始项目信息如下: 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | 可以解决任何类型问题,非线性规划、连续变量、整数变量、01变量均可(可以有等式和不等式约束条件) 3 | """ 4 | import numpy as np 5 | from GA import GA 6 | 7 | #设置目标哈数 8 | def f(X): 9 | return X[0]**2+X[1]**2+8 10 | 11 | #设置不等式约束条件(默认都是要小于等于0) 12 | def ineq_constraints(X): 13 | #约束1 14 | cons1=X[1]-X[0]**2 15 | return [cons1] 16 | 17 | #设置等式约束(右边为0) 18 | def eq_constraints(X): 19 | #约束2 20 | cons2=-X[0]-X[1]**2+2 21 | return [cons2] #组合成序列的形式 22 | 23 | if __name__ == '__main__': 24 | varbound = np.array([[0,100], [0, 100]]) # 变量值域范围 25 | vartype = np.array([['real'],['real']]) # 变量类型 26 | #vartype = np.array([['real'], ['int'], ['int']]) # 变量类型可选,如果01变量也是选择int,变量值域限制为01即可 27 | 28 | #默认是最小化目标函数 29 | model = GA(function=f, #目标函数 30 | dimension=2, #决策变量数量 31 | variable_type=vartype, #变量类型(序列形式,real,int) 32 | variable_boundaries=varbound, #各变量值域 33 | eq_constraints=eq_constraints, #等式约束函数 34 | ineq_constraints=ineq_constraints, #不等式约束函数 35 | function_timeout=10, #延时(函数没响应) 36 | eq_cons_coefficient=0.001, #等式约束系数(值越小,等式约束越严格),如果有时候优化不到可行解,可以适当调大这个值 37 | eq_optimezer='soft', #等式约束优化器(hard,soft),可以都尝试,软约束有时候能优化到更好的最优值,但是有时候会无解或者不是可行解,硬约束会优先确保结果是可行解 38 | max_num_iteration=300, #最大迭代次数 39 | population_size=1000, #个体数量(初始解的数量) 40 | penalty_factor=1, #惩罚因子(用于约束条件),越大约束作用越大(要选择合适的值1比较合适,用于乘上比较0和约束输出,即允许) 41 | mutation_probability=0.1, #变异概率 42 | elit_ratio=0.01, #精英选择的比例(每轮都保留一定数量的较优目标函数的个体) 43 | crossover_probability=0.5, #交叉概率 44 | parents_portion=0.3, #父代比例(用于每代的交叉和变异) 45 | crossover_type='uniform', #交叉类型(”one_point“ ”two_point“ "uniform") 46 | max_iteration_without_improv=None, #多少轮没更新后退出 47 | convergence_curve=True, #是否绘制算法迭代收敛曲线 48 | progress_bar=True, #进度条显示 49 | plot_path='.' #保存收敛曲线svg图像的路径 50 | ) 51 | model.run() 52 | #输出信息 53 | best_variable=model.best_variable #最优解 54 | best_function=model.best_function #最优目标函数 55 | report=model.report #每轮的最优目标函数 56 | -------------------------------------------------------------------------------- /ga.py: -------------------------------------------------------------------------------- 1 | """ 2 | 本项目基于rmsolgi的geneticalgorithm项目进行改进,实现各种约束条件下的目标优化(加入罚函数),原始项目信息请见:https://github.com/rmsolgi/geneticalgorithm 3 | Copyright 2020 Ryan (Mohammad) Solgi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 9 | Software, and to permit persons to whom the Software is furnished to do so, 10 | 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 18 | THE 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 | 23 | """ 24 | 25 | import sys 26 | import time 27 | 28 | from func_timeout import func_timeout, FunctionTimedOut 29 | from tqdm import tqdm 30 | import numpy as np 31 | import matplotlib.pyplot as plt 32 | from matplotlib import rcParams #导入包 33 | 34 | config = {"font.family":'Times New Roman'} # 设置字体类型 35 | rcParams.update(config) #进行更新配置 36 | 37 | class GA(): 38 | def __init__(self, function, dimension, 39 | variable_boundaries=None, 40 | variable_type=None, 41 | eq_constraints=None, 42 | ineq_constraints=None, 43 | function_timeout=10, 44 | eq_cons_coefficient=0.001, 45 | eq_optimezer='soft', #选用的等式约束优化方法(soft,hard),软约束有时候能优化到更好的最优值,但是有时候会无解或者不是可行解,硬约束会优先确保结果是可行解 46 | max_num_iteration=None, 47 | population_size=100, 48 | penalty_factor=1, 49 | mutation_probability=0.1, 50 | elit_ratio=0.01, 51 | crossover_probability=0.5, 52 | parents_portion=0.3, 53 | crossover_type='uniform', 54 | max_iteration_without_improv=None, 55 | convergence_curve=True, 56 | progress_bar=True, 57 | plot_path=None,**kwargs): 58 | 59 | self.plot_path = plot_path # 存储图像的路径 60 | 61 | self.__name__ = GA 62 | 63 | #目标函数 64 | assert (callable(function)), "function must be callable" 65 | self.f = function 66 | 67 | #决策变量数量 68 | self.dim = int(dimension) 69 | 70 | #约束条件 71 | self.eq_constraints = eq_constraints # 注意约束为序列形式,里面是约束函数(等式约束) 72 | self.ineq_constraints = ineq_constraints #不等式约束 73 | 74 | #等式约束系数(用于限制等式约束)越小越严格 75 | self.eq_cons_coefficient=eq_cons_coefficient 76 | 77 | #等式约束优化方法 78 | self.eq_optimezer=eq_optimezer 79 | assert self.eq_optimezer in ['soft','hard'],"eq_optimezer must be 'soft' or 'hard'" 80 | 81 | # 输入变量的类型(连续变量、整数变量) 82 | assert variable_type is not None,'Input variable type cannot be empty' 83 | assert (type(variable_type).__module__ == 'numpy'),"\n variable_type must be numpy array" 84 | assert (len(variable_type) == self.dim), "\n variable_type must have a length equal dimension." 85 | for i in variable_type: # 确保都要为连续变量或者整数变量,如果是01变量就更改其变量的范围即可 86 | assert (i == 'real' or i == 'int'), \ 87 | "\n variable_type_mixed is either 'int' or 'real' " + \ 88 | "ex:['int','real','real']" + \ 89 | "\n for 'boolean' use 'int' and specify boundary as [0,1]" 90 | self.var_type = variable_type 91 | 92 | # 设置变量的范围 input variables' boundaries 93 | assert (type(variable_boundaries).__module__ == 'numpy'), "\n variable_boundaries must be numpy array" 94 | assert (len(variable_boundaries) == self.dim), "\n variable_boundaries must have a length equal dimension" 95 | for i in variable_boundaries: 96 | assert (len(i) == 2), "\n boundary for each variable must be a tuple of length two." 97 | assert (i[0] <= i[1]), "\n lower_boundaries must be smaller than upper_boundaries [lower,upper]" 98 | self.var_bound = variable_boundaries 99 | 100 | # Timeout 超时 101 | self.funtimeout = float(function_timeout) 102 | 103 | # 收敛曲线 convergence_curve 104 | if convergence_curve == True: 105 | self.convergence_curve = True 106 | else: 107 | self.convergence_curve = False 108 | 109 | # 进度条 progress_bar 110 | if progress_bar == True: 111 | self.progress_bar = True 112 | else: 113 | self.progress_bar = False 114 | 115 | # 下面是输入的超参数部分 116 | self.penalty_factor = penalty_factor #惩罚因子 117 | self.pop_s = int(population_size) #个体数量 118 | 119 | assert (parents_portion <= 1 and parents_portion >= 0),"parents_portion must be in range [0,1]" 120 | self.par_s = int(parents_portion * self.pop_s) #下一代中保留的父代数量 121 | trl = self.pop_s - self.par_s 122 | if trl % 2 != 0: #保证父代数量为偶数 123 | self.par_s += 1 124 | 125 | self.prob_mut = mutation_probability # 变异概率 126 | assert (self.prob_mut <= 1 and self.prob_mut >= 0), "mutation_probability must be in range [0,1]" 127 | 128 | self.prob_cross = crossover_probability # 交叉概率 129 | assert (self.prob_cross <= 1 and self.prob_cross >= 0), "cross_probability must be in range [0,1]" 130 | 131 | assert (elit_ratio <= 1 and elit_ratio >= 0), "elit_ratio must be in range [0,1]" 132 | trl = self.pop_s * elit_ratio 133 | if trl < 1 and elit_ratio > 0: 134 | self.num_elit = 1 135 | else: 136 | self.num_elit = int(trl) # 精英选择数量 137 | 138 | assert (self.par_s >= self.num_elit), "\n number of parents must be greater than number of elits" 139 | 140 | if max_num_iteration == None: # 控制最大迭代轮数 141 | self.iterate = 0 142 | for i in range(0, self.dim): 143 | if self.var_type[i] == 'int': 144 | self.iterate += (self.var_bound[i][1] - self.var_bound[i][0]) * self.dim * (100 / self.pop_s) 145 | else: 146 | self.iterate += (self.var_bound[i][1] - self.var_bound[i][0]) * 50 * (100 / self.pop_s) 147 | self.iterate = int(self.iterate) 148 | if (self.iterate * self.pop_s) > 10000000: 149 | self.iterate = 10000000 / self.pop_s 150 | else: 151 | self.iterate = int(max_num_iteration) 152 | 153 | self.c_type = crossover_type # 交叉的方式(单点交叉、双点交叉和均匀交叉)默认为均匀交叉 154 | assert (self.c_type == 'uniform' or self.c_type == 'one_point' or self.c_type == 'two_point'), \ 155 | "\n crossover_type must 'uniform', 'one_point', or 'two_point' Enter string" 156 | 157 | self.stop_mniwi = False 158 | if max_iteration_without_improv == None: 159 | self.mniwi = self.iterate + 1 160 | else: 161 | self.mniwi = int(max_iteration_without_improv) # 最大没有更新最优解的轮数 162 | 163 | # 主要的算法主体部分 164 | def run(self): 165 | # 初始化种群Initial Population 166 | self.integers = np.where(self.var_type == 'int') # 存储整数变量的序号位置 167 | self.reals = np.where(self.var_type == 'real') # 存储连续变量的序号位置 168 | 169 | pop = np.array([np.zeros(self.dim + 1)] * self.pop_s) # 产生的种群,形状为(个体数量,变量数量+1)每个个体的最后一个位置用于存储适应度 170 | solo = np.zeros(self.dim + 1) # 存储单个个体的一个解和其对应的适应度,最后一个位置用于保存适应度 171 | var = np.zeros(self.dim) # 单个个体的决策变量 172 | 173 | # 下面进行随机生成各个变量在值域范围内的值 174 | for p in range(0, self.pop_s): # 每个个体 175 | 176 | for i in self.integers[0]: # 每个整数变量的序号 177 | var[i] = np.random.randint(self.var_bound[i][0], \ 178 | self.var_bound[i][1] + 1) 179 | solo[i] = var[i].copy() 180 | for i in self.reals[0]: # 每个连续变量的序号 181 | var[i] = self.var_bound[i][0] + np.random.random() * \ 182 | (self.var_bound[i][1] - self.var_bound[i][0]) 183 | solo[i] = var[i].copy() 184 | 185 | obj = self.sim(var) # 计算适应度(目标函数值) 186 | solo[self.dim] = obj 187 | pop[p] = solo.copy() # 存储 188 | 189 | # Report 190 | self.report = [] #每轮的最优解 191 | self.best_variable = var.copy() # 最优解 192 | self.best_function = obj # 最优目标函数 193 | with tqdm(total=self.iterate, desc="Processing", unit="iteration") as pbar: #设置进度条 194 | t = 1 #统计迭代轮数 195 | counter = 0 #统计未更新的轮数 196 | while t <= self.iterate: # 每轮更新迭代 197 | # 对适应度进行罚函数处理(更加准确,如果想要保证速度的话可以去除下面的罚函数处理) 198 | for i in range(len(pop)): # 每个个体 199 | pop[i, self.dim] = self.penalty(pop[i, :self.dim]) + pop[i, self.dim] # 适应度+罚函数值 200 | # Sort 201 | pop = pop[pop[:, self.dim].argsort()] # 根据最后一个目标函数进行升序排列 202 | 203 | # 更新目前目标函数值最小的解向量和对应目标函数值 204 | if self.eq_constraints is None and self.ineq_constraints is None: # 无约束条件 205 | if pop[0, self.dim] < self.best_function: 206 | counter = 0 207 | self.best_function = pop[0, self.dim].copy() 208 | self.best_variable = pop[0, : self.dim].copy() 209 | else: 210 | counter += 1 211 | 212 | else: # 有约束条件 213 | for i in range(len(pop)): # 每个个体 214 | if self.penalty(pop[i, :self.dim]) <= 0: # 判断是否可行解(全部小于等于0则说明是可行解) 215 | if pop[i, self.dim] < self.best_function: # 最优解判断 216 | counter = 0 217 | self.best_function = pop[i, self.dim].copy() 218 | self.best_variable = pop[i, : self.dim].copy() 219 | break 220 | else: 221 | counter += 1 222 | break 223 | else: 224 | counter += 1 225 | 226 | # Report 227 | # 记录每轮迭代最优的目标函数值(每次都是历史最优) 228 | self.report.append(self.best_function) 229 | 230 | # 标准化下目标函数 Normalizing objective function 231 | normobj = np.zeros(self.pop_s) # 用于存储修正后的目标函数序列 232 | 233 | minobj = pop[0, self.dim] # 最小目标函数 234 | # 确保所有目标函数大于0 235 | if minobj < 0: 236 | normobj = pop[:, self.dim] + abs(minobj) 237 | 238 | else: 239 | normobj = pop[:, self.dim].copy() 240 | 241 | maxnorm = np.amax(normobj) # 最大目标函数值 242 | normobj = maxnorm - normobj + 1 # 最优的目标函数现在变最大(>0) 243 | 244 | # 进行计算概率(Calculate probability) 245 | sum_normobj = np.sum(normobj) 246 | prob = np.zeros(self.pop_s) 247 | prob = normobj / sum_normobj # 个体概率(适应度越大,则概率越大) 248 | cumprob = np.cumsum(prob) # 累计概率 249 | 250 | # 选择父代(Select parents) 251 | par = np.array([np.zeros(self.dim + 1)] * self.par_s) # 形状为(dim+1,设置留下父母数量) 252 | 253 | par[:self.num_elit] = pop[:self.num_elit].copy() # 进行精英选择(将最好的几个个体直接作为父代保留) 254 | for k in range(self.num_elit, self.par_s): # 其余父代通过概率随机筛选(轮盘赌法) 255 | index = np.searchsorted(cumprob, np.random.random()) # 找到第一个大于或等于随机生成的概率值的位置。返回的索引表示该位置。 256 | par[k] = pop[index].copy() 257 | 258 | ef_par_list = np.array([False] * self.par_s) # 判断是否用于交叉的序列 259 | par_count = 0 # 用于交叉、变异操作的父代总数 260 | while par_count == 0: 261 | for k in range(0, self.par_s): 262 | if np.random.random() <= self.prob_cross: 263 | ef_par_list[k] = True 264 | par_count += 1 265 | 266 | ef_par = par[ef_par_list].copy() # 取出筛选出的用于后续交叉、变异的父代 267 | 268 | # 产生新一代(New generation) 269 | pop = np.array([np.zeros(self.dim + 1)] * self.pop_s) 270 | 271 | # 将事先已经通过精英筛选和轮盘赌法后的优秀父代保留 272 | pop[:self.par_s] = par.copy() 273 | 274 | # 利用筛选的父代进行交叉、变异操作并和选择的优秀父代共同形成新一代 275 | for k in range(self.par_s, self.pop_s, 2): # 步长为2,每轮都会生成2个子代 276 | r1 = np.random.randint(0, par_count) # 随机选择一个父代序号1 277 | r2 = np.random.randint(0, par_count) # 随机选择一个父代序号2 278 | pvar1 = ef_par[r1, : self.dim].copy() # 父代解1 279 | pvar2 = ef_par[r2, : self.dim].copy() # 父代解2 280 | 281 | ch = self.cross(pvar1, pvar2, self.c_type) # 进行交叉操作,返回两个子代解 282 | ch1 = ch[0].copy() # 产生的第一个子代解 283 | ch2 = ch[1].copy() # 产生的第二个子代解 284 | 285 | ch1 = self.mut(ch1) # 对第一个子代进行随机变异 286 | ch2 = self.mutmidle(ch2, pvar1, pvar2) # 对第二个子代进行在两个父代之间的变异 287 | # 进行存储子代解与其适应度 288 | # 子代1 289 | solo[: self.dim] = ch1.copy() 290 | obj = self.sim(ch1) 291 | solo[self.dim] = obj 292 | pop[k] = solo.copy() 293 | # 子代2 294 | solo[: self.dim] = ch2.copy() 295 | obj = self.sim(ch2) 296 | solo[self.dim] = obj 297 | pop[k + 1] = solo.copy() 298 | pbar.update(1)#pbar进行更新 299 | 300 | t += 1 # 迭代轮数+1 301 | if counter > self.mniwi: # 看是否超过最大未更新最优适应度轮数 302 | pop = pop[pop[:, self.dim].argsort()] # 按照适应度升序 303 | if pop[0, self.dim] >= self.best_function: 304 | t = self.iterate+1 305 | time.sleep(2) 306 | self.stop_mniwi = True 307 | 308 | # Sort 309 | pop = pop[pop[:, self.dim].argsort()] # 升序 310 | 311 | # 输出最优目标函数和最优解(可行解) 312 | if self.eq_constraints is None and self.ineq_constraints is None: # 无约束条件 313 | if pop[0, self.dim] < self.best_function: 314 | self.best_function = pop[0, self.dim].copy() 315 | self.best_variable = pop[0, : self.dim].copy() 316 | else: # 有约束条件 317 | for i in range(len(pop)): 318 | if pop[i, self.dim] < self.best_function and self.penalty(pop[i, :self.dim]) <= 0: 319 | self.best_function = pop[i, self.dim].copy() 320 | self.best_variable = pop[i, : self.dim].copy() 321 | break 322 | 323 | # 记录当前最优解Report 324 | self.report.append(self.best_function) 325 | 326 | # 输出字典 327 | self.output_dict = {'variable': self.best_variable, 'function': \ 328 | self.best_function} 329 | if self.progress_bar == True: 330 | show = ' ' * 100 331 | sys.stdout.write('\r%s' % (show)) 332 | judge = '局部最优解是可行解' if self.penalty(self.best_variable) <= 0 else '该局部最优解不可行,找不到可行的局部最优解' 333 | sys.stdout.write('\r The best solution found:\n %s' % (self.best_variable)) 334 | sys.stdout.write('\n\n Objective function:\n %s\n' % (self.best_function)) 335 | sys.stdout.flush() 336 | print(f'{judge}') 337 | re = np.array(self.report) 338 | 339 | if self.convergence_curve == True: # 绘制收敛曲线 340 | plt.figure(figsize=(8, 6)) 341 | plt.tick_params(size=5, labelsize=13) # 坐标轴 342 | plt.grid(alpha=0.3) # 是否加网格线 343 | plt.plot(np.arange(t), re, color='#e74c3c', lw=1.5) 344 | plt.xlabel('Iteration', fontsize=13) 345 | plt.ylabel('Objective function value', fontsize=13) 346 | plt.title('Genetic Algorithm', fontsize=15) 347 | if self.plot_path is not None: 348 | plt.savefig(f'{self.plot_path}/Genetic Algorithm.svg', format='svg', bbox_inches='tight') 349 | plt.show() 350 | 351 | if self.stop_mniwi == True: # 退出循环 352 | sys.stdout.write('\nWarning: GA is terminated due to the' + \ 353 | ' maximum number of iterations without improvement was met!') 354 | 355 | def penalty(self, X): # 根据所有约束条件计算惩罚项 356 | if self.eq_constraints is not None: #有等式约束时 357 | eq_cons_output = self.eq_constraints(X) # 输出约束条件值 358 | #将等式约束拆分成两个不等式约束(一个小于等于,一个大于等于),这个更加标准,但是迭代到最优解有点慢(硬约束) 359 | if self.eq_optimezer=='hard': 360 | eq_cons_output= [output-self.eq_cons_coefficient for output in eq_cons_output]+[-output-self.eq_cons_coefficient for output in eq_cons_output] 361 | #下面的更快更准(但是可能有时候会有问题) 362 | else: 363 | eq_cons_output= [output-self.eq_cons_coefficient-self.eq_cons_coefficient*2*i 364 | for i in range(2) 365 | for output in eq_cons_output] 366 | eq_conpare_matrix = np.zeros((len(eq_cons_output), 2)) 367 | eq_conpare_matrix[:, 1] = eq_cons_output 368 | eq_penalty=self.penalty_factor * np.sum(np.max(eq_conpare_matrix, axis=1)) #注意python中对于极小的大于等于0的数也是默认于0的所以这样来限制,self.penalty_factor也是用来放缩的权重 369 | 370 | else: 371 | eq_penalty=0 372 | 373 | if self.ineq_constraints is not None: #有不等式约束时 374 | ineq_cons_output = self.ineq_constraints(X) # 输出约束条件值 375 | ineq_conpare_matrix = np.zeros((len(ineq_cons_output), 2)) 376 | ineq_conpare_matrix[:, 1] = ineq_cons_output 377 | ineq_penalty=self.penalty_factor * np.sum(np.max(ineq_conpare_matrix, axis=1)) 378 | else: 379 | ineq_penalty=0 380 | 381 | if self.eq_constraints is not None or self.ineq_constraints is not None: # 没有约束条件时 382 | return eq_penalty+ineq_penalty 383 | else: 384 | return 0 385 | 386 | def cross(self, x, y, c_type): # 交叉 387 | 388 | ofs1 = x.copy() 389 | ofs2 = y.copy() 390 | 391 | # 这里的遗传算法是通过直接调换两个解的对应顺序 392 | if c_type == 'one_point': # 单点交叉 393 | ran = np.random.randint(0, self.dim) # 选择点位 394 | ofs1[:ran] = y[:ran].copy() 395 | ofs2[:ran] = x[:ran].copy() 396 | 397 | if c_type == 'two_point': 398 | # 随机选择两个点位 399 | ran1 = np.random.randint(0, self.dim) 400 | ran2 = np.random.randint(ran1, self.dim) 401 | # 进行交叉(交换) 402 | ofs1[ran1:ran2] = y[ran1:ran2].copy() 403 | ofs2[ran1:ran2] = x[ran1:ran2].copy() 404 | 405 | if c_type == 'uniform': # 均匀交叉 406 | 407 | for i in range(0, self.dim): 408 | ran = np.random.random() 409 | if ran < 0.5: 410 | ofs1[i] = y[i].copy() 411 | ofs2[i] = x[i].copy() 412 | 413 | return np.array([ofs1, ofs2]) # 返回两个子代解 414 | 415 | def mut(self, x): # 变异(对每个决策变量都有小的概率重新生成一个在值域范围内的值) 416 | 417 | for i in self.integers[0]: # 对整数决策变量 418 | ran = np.random.random() 419 | if ran < self.prob_mut: 420 | x[i] = np.random.randint(self.var_bound[i][0], \ 421 | self.var_bound[i][1] + 1) 422 | 423 | for i in self.reals[0]: # 对连续的决策变量 424 | ran = np.random.random() 425 | if ran < self.prob_mut: 426 | x[i] = self.var_bound[i][0] + np.random.random() * \ 427 | (self.var_bound[i][1] - self.var_bound[i][0]) 428 | 429 | return x # 返回解 430 | 431 | def mutmidle(self, x, p1, p2): # 在两个父代解中进行变异,值域限制在父代两个解之间 432 | for i in self.integers[0]: # 对整数变量 433 | ran = np.random.random() 434 | if ran < self.prob_mut: 435 | if p1[i] < p2[i]: 436 | x[i] = np.random.randint(p1[i], p2[i]) 437 | elif p1[i] > p2[i]: 438 | x[i] = np.random.randint(p2[i], p1[i]) 439 | else: 440 | x[i] = np.random.randint(self.var_bound[i][0], \ 441 | self.var_bound[i][1] + 1) 442 | 443 | for i in self.reals[0]: # 对连续变量 444 | ran = np.random.random() 445 | if ran < self.prob_mut: 446 | if p1[i] < p2[i]: 447 | x[i] = p1[i] + np.random.random() * (p2[i] - p1[i]) 448 | elif p1[i] > p2[i]: 449 | x[i] = p2[i] + np.random.random() * (p1[i] - p2[i]) 450 | else: 451 | x[i] = self.var_bound[i][0] + np.random.random() * \ 452 | (self.var_bound[i][1] - self.var_bound[i][0]) 453 | return x 454 | 455 | def evaluate(self): # 评估适应度(加入惩罚) 456 | return self.f(self.temp) + self.penalty(self.temp) 457 | 458 | def sim(self, X): # 嵌套了测试目标函数是否可用的一个功能,实际还是返回目标函数 459 | self.temp = X.copy() 460 | obj = None 461 | try: 462 | obj = func_timeout(self.funtimeout, self.evaluate) 463 | except FunctionTimedOut: 464 | print("given function is not applicable") 465 | assert (obj != None), "After " + str(self.funtimeout) + " seconds delay " + \ 466 | "func_timeout: the given function does not provide any output" 467 | return obj 468 | -------------------------------------------------------------------------------- /images/Algorithm_iteration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leost123456/Genetic-Algorithm/5c89c8e44b8fa98612711f69c1c1b5854eb08d1e/images/Algorithm_iteration.png -------------------------------------------------------------------------------- /images/formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leost123456/Genetic-Algorithm/5c89c8e44b8fa98612711f69c1c1b5854eb08d1e/images/formula.png -------------------------------------------------------------------------------- /images/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leost123456/Genetic-Algorithm/5c89c8e44b8fa98612711f69c1c1b5854eb08d1e/images/output.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | func_timeout==4.3.5 2 | matplotlib==3.7.1 3 | numpy==1.20.1 4 | tqdm==4.65.0 5 | 6 | --------------------------------------------------------------------------------