From a0045c341bb54b492ac2a5d479d5be448470ba2e Mon Sep 17 00:00:00 2001 From: Yofuria <20377277@buaa.edu.cn> Date: Wed, 2 Aug 2023 18:25:37 +0800 Subject: [PATCH] add GeneticAlgorithm --- .idea/.name | 2 +- Environment.py | 105 +++++++++++++++++++++-- demand23.xlsx | Bin 9592 -> 9839 bytes ga_new.py | 227 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 8 deletions(-) create mode 100644 ga_new.py diff --git a/.idea/.name b/.idea/.name index 9d346b1..883208b 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -Order.py \ No newline at end of file +ga_new.py \ No newline at end of file diff --git a/Environment.py b/Environment.py index e8fc976..143c379 100644 --- a/Environment.py +++ b/Environment.py @@ -9,6 +9,7 @@ import json from Firm import Firm # import passive agents from Order import Order +from ga_new import GeneticAlgorithm from fake_api import get_plan_by_pp_id, get_bom_by_prd_id @@ -23,7 +24,7 @@ class FMSEnv(ap.Model): # record data, define below # op_os_n_total_order: int # op_os_n_total_order_delayed: int - op_os_all_delay_time: list + op_os_all_delay_time: float # op_os_delay_ratio: float # op_is_flt_material_room_left: float # op_is_flt_product_room_left: float @@ -55,6 +56,7 @@ class FMSEnv(ap.Model): # self.ev_n_order_created = 0 self.op_os_n_total_order = 0 self.op_os_int_status = 0 + self.op_os_all_delay_time = 0 self.running = True self.t = 0 @@ -82,9 +84,10 @@ class FMSEnv(ap.Model): if self.t >= self.int_stop_time: self.running = False self.stop() - else: - print(f"running the {self.t} step") - print("当期延误时长为:{}".format(self.the_firm.the_os.ev_ave_delay_time)) + # else: + # + # # print(f"running the {self.t} step") + # # print("当期延误时长为:{}".format(self.the_firm.the_os.ev_ave_delay_time)) # Record data after each simulation def update(self): # ? @@ -106,10 +109,20 @@ class FMSEnv(ap.Model): self.record([att for att in self.__dict__.keys() if att.startswith('op_')]) # ? + self.op_os_all_delay_time += self.the_firm.the_os.ev_ave_delay_time + # pass -if __name__ == '__main__': +def GA_run(inventory_bound=None): + material = tuple(pd.read_excel("initial_material.xlsx").iloc[:, 0]) + s = tuple(tuple([i, j]) for i, j in + zip(material, inventory_bound[: len(pd.read_excel("initial_material.xlsx").to_numpy())])) + S = tuple(tuple([i, j]) for i, j in zip(material, inventory_bound[ + len(pd.read_excel("initial_material.xlsx").to_numpy()): len( + pd.read_excel("initial_material.xlsx").to_numpy()) * 2])) + # print(s) + # print(S) dct_para = { 'time': 300, # 进行总时间数 # 'xv_int_max_order': random.randint(30, 50), @@ -134,8 +147,8 @@ if __name__ == '__main__': # 初始原材料库存 115x2 'xv_ary_bom': tuple([tuple(x) for x in pd.read_excel("bom23.xlsx").values]), # bom表 'xv_ary_plan': tuple([tuple(x) for x in pd.read_excel("plan.xlsx").values]), # plan表 - 'xv_ary_s': tuple([tuple(x) for x in pd.read_excel("rawmaterial - s.xlsx").values]), # s - 'xv_ary_S': tuple([tuple(x) for x in pd.read_excel("rawmaterialS.xlsx").values]), # S + 'xv_ary_s': s, # s + 'xv_ary_S': S, # S # 应读取遗传算法中随机生成的s,暂写为'1' 创建两个excel分别存储产品和原材料的库存 每个excel中存系统代码和库存 # 'xv_flt_initial_cash': 50000.0, # 'dct_status_info': json.dumps({ #需要引入生产状态表 @@ -176,5 +189,83 @@ if __name__ == '__main__': exp = ap.Experiment(FMSEnv, sample, iterations=1, record=True) results = exp.run() + return results['variables']['FMSEnv']['op_os_all_delay_time'][dct_para['time']] / 2 + + +if __name__ == '__main__': + # dct_para = { + # 'time': 60, # 进行总时间数 + # # 'xv_int_max_order': random.randint(30, 50), + # # 'xv_dlv_product_para': tuple([(30, 100), (30, 50)]), + # # 'xv_dlv_product_para': tuple([30,40,30,20]), # 读取生产率 np.read. + # # 'xv_int_dlv_period_lam': 8.5, + # # 'xv_int_create_order_lam': 2, + # # 'xv_ary_price_product': tuple([0.3,0.2,0.5,1]), + # # 'xv_ary_cost_material_per': tuple([0.1,0.1,0.2,0.4]), + # # 'xv_ary_volume_material': tuple([1.0, 1.5]), + # # 'xv_ary_volume_product': tuple([3.0, 5.0]), + # # 'xv_array_lead_time': 2, # 读取原材料表格 np.read, 暂时不读 变量代表的含义 + # # 'xv_int_lead_time_c': 3, + # # 'xv_int_lead_time_d': 1, + # 'xv_ary_product_id': tuple(pd.read_excel("initial_product.xlsx").iloc[:, 0]), # 产成品id顺序 + # 'xv_ary_material_id': tuple(pd.read_excel("initial_material.xlsx").iloc[:, 0]), # 原材料id顺序 + # 'xv_product_num': len(pd.read_excel("initial_product.xlsx").to_numpy()), # 产成品个数 + # 'xv_material_num': len(pd.read_excel("initial_material.xlsx").to_numpy()), # 原材料个数 + # 'xv_ary_initial_product_num': tuple([tuple(x) for x in pd.read_excel("initial_product.xlsx").values]), + # # 初始产成品库存 23x2 + # 'xv_ary_initial_material_num': tuple([tuple(x) for x in pd.read_excel("initial_material.xlsx").values]), + # # 初始原材料库存 115x2 + # 'xv_ary_bom': tuple([tuple(x) for x in pd.read_excel("bom23.xlsx").values]), # bom表 + # 'xv_ary_plan': tuple([tuple(x) for x in pd.read_excel("plan.xlsx").values]), # plan表 + # 'xv_ary_s': tuple([tuple(x) for x in pd.read_excel("rawmaterial - s.xlsx").values]), # s + # 'xv_ary_S': tuple([tuple(x) for x in pd.read_excel("rawmaterialS.xlsx").values]), # S + # # 应读取遗传算法中随机生成的s,暂写为'1' 创建两个excel分别存储产品和原材料的库存 每个excel中存系统代码和库存 + # # 'xv_flt_initial_cash': 50000.0, + # # 'dct_status_info': json.dumps({ #需要引入生产状态表 + # # "0": {"xv_flt_produce_rate": tuple([0.0, 0.0]), + # # "xv_ary_mat_material": tuple([0.0, 0.0]), + # # "xv_flt_broken_rate": 0, + # # "xv_flt_run_cost": 0.0, + # # "name": "wait" + # # }, + # # "1": {"xv_flt_produce_rate": tuple([90.0, 0.0]), + # # "xv_ary_mat_material": tuple([4.0, 1.0]), + # # "xv_flt_broken_rate": 0.03, + # # "xv_flt_run_cost": 40.0, + # # "name": "produceA" + # # }, + # # "2": {"xv_flt_produce_rate": tuple([0.0, 60.0]), + # # "xv_ary_mat_material": tuple([1.5, 5.0]), + # # "xv_flt_broken_rate": 0.05, + # # "xv_flt_run_cost": 50.0, + # # "name": "produceB" + # # }, + # # "3": {"xv_flt_produce_rate": tuple([55.0, 30.0]), + # # "xv_ary_mat_material": tuple([2.0, 1.5]), + # # "xv_flt_broken_rate": 0.07, + # # "xv_flt_run_cost": 60.0, + # # "name": "produceAB" + # # }, + # # "-1": {"xv_flt_produce_rate": 0.0, + # # "xv_ary_mat_material": tuple([0.0, 0.0]), + # # "xv_flt_broken_rate": 0.1, + # # "xv_flt_run_cost": 100.0, + # # "name": "failed" + # # } + # # }) + # + # } + # sample = ap.Sample(dct_para) + # + # exp = ap.Experiment(FMSEnv, sample, iterations=1, record=True) + # results = exp.run() + # print(results['variables']['FMSEnv']['op_os_all_delay_time']) + # print(results['variables']['FMSEnv']['op_os_all_delay_time'][dct_para['time']]) # results['variables']['FMSEnv'].to_excel(f"simulation-results-{datetime.today().strftime('%Y-%m-%d-%H-%M-%S')}.xlsx", # engine='openpyxl') + + material_num = len(pd.read_excel("initial_material.xlsx").to_numpy()) # 原材料个数 + GA = GeneticAlgorithm(function=GA_run, dim=material_num * 2, lb=[10 for i in range(material_num * 2)], + ub=[100 for i in range(material_num * 2)], int_var=[i for i in range(material_num * 2)]) + GA.optimize() + # print(result1, result2) diff --git a/demand23.xlsx b/demand23.xlsx index 37319265326c70e86792f13004c39799e904ce86..2cc2978c5e39fccbb2c5c13c7229efc078ff1b94 100644 GIT binary patch delta 2075 zcmYM#dpy(o9|v$7qFhcq+~zWDQ@KW8Q=-i!Ys0Z3v>%!47PVGtup#TJhDNpnsQh-S%e!q*rs@ZEE1uTfbra27tMQ(zC&8;YKEN=BWjU& zysD}%%QtIE{Y$B_LK!nDg1O)_Cub8WYpJAD)@EziiuI9cG^P1m$kd`TM_B(Z7>bG+ zNvh~5O9-hdt1Kz;_z>=2zX@T(3(uRoZ#fS&eY&23k9>nR^;!u0Yr8(b#n=aqXa@Ys zj^+RXso^6GX=f@iXS!6e!L&u@gjgMN-1=CT)>n(TRE83zR0b$B+UT3NgnvhVc? z=pav0Oze(2_(4LO#tQpA54XQ-!gC?UrrFVp^P&|KS*0~Lql9+Va0nh{&#rDTpq-M- z`H8|_6AL0BJ;&h)sNb-F&}%ld`I_KWcY){m52QF=edtV6I_AlYi4`V-*V1w=w$!^g zJma&;e|n0>>#@;G?2p@2Go!S=@8f`!tL zg0Ww(H5pjiR}9DR(Zkew9>=OY+(aqvD zOfj~Xh@?Uq8XMX)oD@AM%zxL%Xpo%y9Cs3uwLd_)>vIycCslnG1_G=!rP6io~L8s* z2ziU{EyHo_DD5Qa-hP_J?KnC8W|OYfb@YT(LO2cM+3T_bW;e%?XMa+fW?w`F2uiN% z_qBXo{PE7lY|1feSf8A0ZMA^qm-hOl=qnz193)@6I;el-Qup~{JiAV=CM!hmI5yS3rS0+vE#y2u^ zyl5$7k}uqzDXiQHsjyW=KdtbhExmf3^(dPzMgLM>xtuFiUVuJ(@|?!yP_5{mur(>S z&T#^0vT*lo{9JAEly3dS%Al;pE+Tm%vEe8G`)oEjKznx@LmroMSBBymD_SE zr&L5$EMz@*UH8Sk%4J@!R==E&F665prploL04wffg&A|`&F5zE)LPnk`ooB&wlQR^ zrr_w>0ya0bZ#|9QR1`OLJOABc`g8*v}h?G$g@|cK9N#x2h-M@ zuFU_)BIzmJ|H^`U$Px`Lv^@7$|9MHRBzE~mzxeH`TbvE6&jWwU0fy4=d3;~~yQ;P6 zP-2%#htW6g)tSC^XWB0&HxH`jrJK-6TunTjpMb{r@mKw0<16RCC*^Rsj-4Up)cEUW z)@|o#G2X`}OxrW3`V^HmShe&Msaz@M(p(es;M+qT47*%~?x!)i zXXvmLnmO{WsEF7py7kd15=YV=!5nhVoCKh-xB&)&d*~0?Zw%{ToB~CupJ9V5G%u0{ z(k|e1h$k%HN(U+tfKNFJ#iA#gz$cMM55VQ`NNbN^QC#}qKfjRau)Vu2h_*Yh!vI5q zg4#i7NMbItl_9YsO%njc9OeU&1O*g#q~gL1Q62&pKOj{gM*v88UI_yAmS`kMI1B}R|dBc22xQHVq&qOhrvmdCg_BehlXo|UPv_b zo;KJB*96Ov22ivHIEu7^Y=au81zCc_lSw9 WiHQzGOlRl922bf3?r}xz*8U4+Y~ObP delta 1859 zcmZA2X*k>28UXMlv_YS$s%v;3nz5EzrV(Tnp_WXQ)(z6eKQn_8 zODH-WDT?s6xTLAzTl2jo%1tUrrpdu)`=Zc!Z|po zI6rYQ>cL5FW}G|uCp`sA?SeMO#ZK6;nC^W``B*EFjtUHB1)O(7-c4epYNK{aSAk)p z#epJc$>(~L+@>JA$52ASR8Yw1W*1!@5t4ByP`N%fUB$+8j=QhWWmc;oRu7iet4o!i z7YBiuiXe~@2(X4JQ?hd2V^|JyKTO)^QEXl`;PK?Jre8BkzKgI!Z%r^I4LER{_##p60kMP`tLA_)jqq-DCfFA#uu{oM+)x+5hj;O zgKJd3I>IdF4R^nBZ>N0Akz}}c$!Yc0z(dn=5Y{C~ z?}qATeBc*9ly03fIi6R7-lOA&_9Sdqx4re$ukMV$`SIR;3`-tW>rDd>I z`AC1yIAATFa>+BuynHAEkNB8Tvloy?x_w2#A!&a+@|~qzBfnv}rMbaiw2CahR^q`U z5ncJ@LNvs-uGJFygtkRO#B#F zkW7EKaVgY%P}42Z?d4Jw8H2 zJCWa8Q*_si@a8ZB4#eFMMQnqQmwLs-?Fey%2^8^ib$ekCUGhRm3#=f8)x)vJ{R2w# zed1U?H45esgHhp_G*R3R)+J6cjy#BwLaN~q(0-o-&hd@B%Toc2;k+ z)QjH@X3sZ6_DhWZKEo{OAhl6w7Sg7s3BI4*z!72{bw3y4jE^no!%@&Q|I59-f9A7` zi>%}@Cyh;^3xB{-ifR6Ky}d0v2&r-VX$4tRBJNdoO04Izvi4rpe0EvUOF4}3SE`3H zOY=t_(3?`@Zqo{Krl$P+*hLvP73Id`9U_cHIeZN%{}umV2xn$Y7)}gC!+)NiinZ-? zRedGEw1vej=2?b~l4@aIhzK$#uXnLDP(0G49QasO5Bn+1q^4n!BwHPy`IHT>*?w@| z{36E{w%Ql!dWR9g6OSasc`}(FhS9FHdrd&?22FE`0-sSk0~_hLpjNQb$*DO;@%AT4 zavL<6rrsP;>atD?S%S}thzr-lMXlq7R(oof1+|iO&=gtYZ#MPN$}YjD5A9_&okQzX z!8&M)tR#uSr^%dx@!-WYPwvT(-*eY#G8Dj(>*1tU_6=9E7>8Jo#1M54aM?F3pvfFq zJGJmb+g-uRJhb1D7^3EZWzkGTgIoVr3;&l#zs2Ph6QU?J>9XjqWaS@Pb5a-=8~8;( zDXG!MCFcbTn~&&@w@;S@$P6!gbDl(XPr%_rgH(jy;Z#(j7~-|2VZesQbmvF0TB#Wnhx3CT&t#`#rFUzcIVNyCya87{1J#RMOcKY{*q0dm<3SOd5 z)(|_~unz|C;>xv4QrU`-HgzCb(;A|$2E5iZfw-vw8=BeQJ|wEvRj@oTsbvGk0UFvy zQ1WRIC@#tnXwg&vqP3MF%+o-%x++kneHL<285q>IIS!TiDu^-hQQ<^Dprrv^)RBga zNdicn^I#B=s$&BgQ2{!2EUf6F^(&f1#u6flsPOO@qM^Gp7$OIf1A#yipu5N!oBv(X WqO$0KK^mg_C2$d@f7JiXLFGSP+lgfW diff --git a/ga_new.py b/ga_new.py new file mode 100644 index 0000000..330e61e --- /dev/null +++ b/ga_new.py @@ -0,0 +1,227 @@ +import numpy +import numpy as np + + +class GeneticAlgorithm: + """Genetic algorithm. + Implementation of the real-valued Genetic algorithm. The mutations are + normally distributed perturbations, the selection mechanism is a tournament + selection, and the crossover oepration is the standard linear combination + taken at a randomly generated cutting point. + The total number of evaluations are popsize x ngen + :param function: Function that can be used to evaluate the entire + population. It needs to take an input of size pop_size x dim and + return a numpy.array of size pop_size x 1 + :type function: Object + :param dim: Number of dimensions + :type dim: int + :param lb: Lower variable bounds, of length dim + :type lb: numpy.array + :param ub: Lower variable bounds, of length dim + :type ub: numpy.array + :param int_var: List of indices with the integer valued variables + (e.g., [0, 1, 5]) + :type int_var: list + :param pop_size: Population size + :type pop_size: int + :param num_gen: Number of generations + :type num_gen: int + :param start: Method for generating the initial population + :type start: string + :ivar nvariables: Number of variables (dimensions) + :ivar nindividuals: population size + :ivar lower_boundary: lower bounds for the optimization problem + :ivar upper_boundary: upper bounds for the optimization problem + :ivar integer_variables: List of variables that are integer valued + :ivar start: Method for generating the initial population + :ivar sigma: Perturbation radius. Each pertubation is N(0, sigma) + :ivar p_mutation: Mutation probability (1/dim) + :ivar tournament_size: Size of the tournament (5) + :ivar p_cross: Cross-over probability (0.9) + :ivar ngenerations: Number of generations + :ivar function: Object that can be used to evaluate the objective function + """ + + def __init__(self, function, dim, lb, ub, int_var=None, pop_size=20, num_gen=300, start="Random"): + + self.nvariables = dim # column + self.nindividuals = pop_size + (pop_size % 2) # Make sure this is even row + self.lower_boundary = np.array(lb) + self.upper_boundary = np.array(ub) + self.integer_variables = [] + if int_var is not None: + self.integer_variables = np.array(int_var) + self.start = start + self.sigma = 0.2 + self.p_mutation = 1.0 / dim + self.tournament_size = 5 + self.p_cross = 0.9 + self.ngenerations = num_gen + self.function = function + + def optimize(self): + """Method used to run the Genetic algorithm + :return: Returns the best individual and its function value + :rtype: numpy.array, float + """ + # Initialize population + if isinstance(self.start, np.ndarray): + if self.start.shape[0] != self.nindividuals or self.start.shape[1] != self.nvariables: + raise ValueError("Initial population has incorrect size") + if any(np.min(self.start, axis=0) < self.lower_boundary) or any( + np.max(self.start, axis=0) > self.upper_boundary + ): + raise ValueError("Initial population is outside the domain", self.lower_boundary, self.upper_boundary, + self.start) + population = self.start + elif self.start == "SLHD": + from pySOT.experimental_design import SymmetricLatinHypercube + + exp_des = SymmetricLatinHypercube(self.nvariables, self.nindividuals) + population = self.lower_boundary + exp_des.generate_points() * (self.upper_boundary - self.lower_boundary) + elif self.start == "LHD": + from pySOT.experimental_design import LatinHypercube + + exp_des = LatinHypercube(self.nvariables, self.nindividuals) + population = self.lower_boundary + exp_des.generate_points() * (self.upper_boundary - self.lower_boundary) + elif self.start == "Random": + population = self.lower_boundary + np.random.rand(self.nindividuals, self.nvariables) * ( + self.upper_boundary - self.lower_boundary + ) + else: + raise ValueError("Unknown argument for initial population") + + new_population = [] + # Round positions + if len(self.integer_variables) > 0: # 对特定列进行操作 + new_population = np.copy(population) + population[:, self.integer_variables] = np.round(population[:, self.integer_variables]) + for i in self.integer_variables: + ind = np.where(population[:, i] < self.lower_boundary[i]) + population[ind, i] += 1 + ind = np.where(population[:, i] > self.upper_boundary[i]) + population[ind, i] -= 1 + + # Evaluate all individuals + # function_values = self.function(population) we cannot compute in this way to ensure x is one-dim in policy + n_row, n_dim = population.shape + function_values = [] + for r in range(n_row): + function_values.append(self.function(population[r, :])) + function_values = np.array(function_values) + + if len(function_values.shape) == 2: + function_values = np.squeeze(np.asarray(function_values)) + + # Save the best individual + ind = np.argmin(function_values) + best_individual = np.copy(population[ind, :]) # 找到最优个体 + best_value = function_values[ind] + + if len(self.integer_variables) > 0: + population = new_population + + # Main loop + for _ in range(self.ngenerations): + print('------------------------------') + print("当前为第{}代".format(_)) + print("最优个体为:{}".format(best_individual)) + print("最优值为:{}".format(best_value)) + print("------------------------------") + # Do tournament selection to select the parents + competitors = np.random.randint(0, self.nindividuals, (self.nindividuals, self.tournament_size)) + ind = np.argmin(function_values[competitors], axis=1) + winner_indices = np.zeros(self.nindividuals, dtype=int) + for i in range(self.tournament_size): # This loop is short + winner_indices[np.where(ind == i)] = competitors[np.where(ind == i), i] + + parent1 = population[winner_indices[0: self.nindividuals // 2], :] + parent2 = population[winner_indices[self.nindividuals // 2: self.nindividuals], :] + + # Averaging Crossover + cross = np.where(np.random.rand(self.nindividuals // 2) < self.p_cross)[0] + nn = len(cross) # Number of crossovers + alpha = np.random.rand(nn, 1) + + # Create the new chromosomes + parent1_new = np.multiply(alpha, parent1[cross, :]) + np.multiply(1 - alpha, parent2[cross, :]) + parent2_new = np.multiply(alpha, parent2[cross, :]) + np.multiply(1 - alpha, parent1[cross, :]) + parent1[cross, :] = parent1_new + parent2[cross, :] = parent2_new + population = np.concatenate((parent1, parent2)) + + # Apply mutation + scale_factors = self.sigma * (self.upper_boundary - self.lower_boundary) # Scale + perturbation = np.random.randn(self.nindividuals, self.nvariables) # Generate perturbations + perturbation = np.multiply(perturbation, scale_factors) # Scale accordingly + perturbation = np.multiply( + perturbation, (np.random.rand(self.nindividuals, self.nvariables) < self.p_mutation) + ) + + perturbation = round_vars(perturbation, self.integer_variables, self.lower_boundary, self.upper_boundary) + population = round_vars(population, self.integer_variables, self.lower_boundary, self.upper_boundary) + + population += perturbation # Add perturbation + population = np.maximum(np.reshape(self.lower_boundary, (1, self.nvariables)), population) + population = np.minimum(np.reshape(self.upper_boundary, (1, self.nvariables)), population) + + # Round chromosomes + new_population = [] + if len(self.integer_variables) > 0: + new_population = np.copy(population) + population = round_vars(population, self.integer_variables, self.lower_boundary, self.upper_boundary) + + # Keep the best individual + population[0, :] = best_individual + + # Evaluate all individuals + # function_values = self.function(population) we cannot compute in this way to ensure x is one-dim in policy + n_row, n_dim = population.shape + function_values = [] + for r in range(n_row): + function_values.append(self.function(population[r, :])) + function_values = np.array(function_values) + + if len(function_values.shape) == 2: + function_values = np.squeeze(np.asarray(function_values)) + + # Save the best individual + ind = np.argmin(function_values) + best_individual = np.copy(population[ind, :]) + best_value = function_values[ind] + + # Use the positions that are not rounded + if len(self.integer_variables) > 0: + population = new_population + + # return best_individual, best_value + + +def round_vars(x: np.ndarray, int_var, lb, ub): + """Round integer variables to closest integer in the domain. + :param x: Set of points, of size npts x dim + :type x: numpy.array + :param int_var: Set of indices of integer variables + :type int_var: numpy.array + :param lb: Lower bounds, of size 1 x dim + :type lb: numpy.array + :param ub: Upper bounds, of size 1 x dim + :type ub: numpy.array + :return: The set of points with the integer variables + rounded to the closest integer in the domain + :rtype: numpy.array + """ + # Make sure we don't violate the bound constraints + for i in int_var: + ind = np.where(x[:, i] < lb[i]) + x[ind, i] = lb[i] + ind = np.where(x[:, i] > ub[i]) + x[ind, i] = ub[i] + + if len(int_var) > 0: + # Round the original ranged integer variables + x[:, int_var] = np.round(x[:, int_var]) + x = x.astype(numpy.int32, copy=True) + else: + x = x.astype(numpy.float64, copy=True) + return x