Spaces:
Runtime error
Runtime error
| import numpy as np | |
| def normaliazed_direction(ori, des): | |
| dir_ = des-ori | |
| nor_dir_ = dir_/np.sqrt(np.sum(dir_**2)) | |
| nor_dir_=np.nan_to_num(nor_dir_) | |
| return nor_dir_ | |
| def distance(ori, des): | |
| dis_ = des-ori | |
| return dis_ | |
| def acc(F, m): | |
| Npar=F.shape[0] | |
| Nvar=F.shape[1] | |
| # Acceleration Matrix | |
| Ac = np.full((Npar, Nvar), 0.0) | |
| for ind_r, row in enumerate(Ac): | |
| for ind_c, col in enumerate(row): | |
| Ac[ind_r][ind_c]=F[ind_r][ind_c]/m[ind_r] | |
| return Ac | |
| def F_coulomb(x, v, q, P, minimize=True, k=0.0001, weight_method='Linear', t=1.0, max_q=100.0): | |
| """ The procedure that generates the new positions of the particles is based on how particles with positive and negative charges move | |
| in electric fields. Thus, the procedure will be as follows: | |
| 1. n particles are created at random positions within the search space. | |
| 2. Each particle is assigned a positive charge type. | |
| 3. The magnitude of each particle's charge is directly related to the objective function. | |
| 3.1 Assignment Methods -> Linear, Exponential, Quadratic (better assignment methods can be explored) | |
| 3.2 Simply put, a solution range is established and adjusted to a charge range between 0 to max_q according to some assignment method. | |
| 4. Each particle holds a velocity. These start at rest but will change over the iterations of the algorithm. | |
| 5. The P particles with the best values obtained from the iteration will remain at rest and emit an electric field of opposite sign (negative) attracting the rest. | |
| 5.1 Let E = k*Q/r^2 be the magnetic field at a point located at a distance r from the source. | |
| 5.2 Let Fe be the electric force between the magnetic field and the particle -> Fe = E*q0 where q0 is the particle's charge. | |
| 5.3 Let Fij be the force produced between particle i and j -> Fij = k*qi*qj/(r^2) where r is the distance between the particles, qi and qj are the charges of i and j. | |
| 5.4 Thus, the resultant forces of particle i are FRi = sum_{p=1}^P k*Qp*Qi/(rip^2) + sum_{j=P+1}^n k*Qi*Qj/(rij^2) | |
| 5.5 Moreover, by Newton's Law, F=m*a -> Fe=m*a. Thus, the velocities of each particle are calculated vf=vi+a*t -> vf = vi + (FRi/m)*t | |
| 5.6 The positions are updated as follows x = vi*t + (a*t^2)/2 -> x = vi*t + (FRi/m)*(t^2)/2 | |
| Finally, if the particles are too close, it could cause a problem. If rij^2 << 0, then the electric force would be too strong, leading | |
| the particles to abruptly separate from each other. To avoid this behavior, a small distance (0.00000001) is established. If the distance | |
| between two particles is smaller than that distance, it will be considered that both particles have collided. Then, if more than half of | |
| the particles have collided, we will apply a random function to generate new solutions over a reduced search space. | |
| Args: | |
| x (array(size=(Npar, Nvar)): current particle positions | |
| v (array(size=(Npar, Nvar)): current particle velocity | |
| q (array(size=(Npar, 1))): current electric charge of each particle | |
| P (int): quantity of electric fields (the best P particles become electric fields) | |
| minimize (bool, optional): solver objective. Defaults to True. | |
| k (float, optional): electric constant. Defaults to 0.0001. | |
| weight_method (str, optional): method to reassign electric charges. Defaults to 'Linear'. Options=['Linear', 'Quadratic', 'Exponential']. | |
| t (float, optional): time. Defaults to 1.0. | |
| max_q (float, optional): upper bound of electric charge to assing. Defaults to 100.0. | |
| Returns: | |
| F_total, Ac, vf, new_pos: returns the force obtained on each particle, their acceleration, their velocities | |
| and their new positions | |
| """ | |
| Npar=x.shape[0] | |
| Nvar=x.shape[1] | |
| # Distance Matrix | |
| dis = [] | |
| for ind_r, row in enumerate(x): | |
| for ind_c, col in enumerate(x): | |
| dis.append(distance(x[ind_r], x[ind_c])) | |
| dis=np.array(dis).reshape((Npar,Npar,Nvar)) | |
| # Direction Matrix | |
| d = [] | |
| for ind_r, row in enumerate(x): | |
| for ind_c, col in enumerate(x): | |
| d.append(normaliazed_direction(x[ind_r], x[ind_c])) | |
| d=np.array(d).reshape((Npar,Npar,Nvar)) | |
| colisioned_part = [] | |
| r_2 = np.zeros((Npar, Npar)) | |
| for ind_r, row in enumerate(dis): | |
| for ind_c, col in enumerate(dis): | |
| value = dis[ind_r][ind_c] | |
| value_2 = value**2 | |
| value_sum = np.sum(value_2) | |
| if value_sum < 0.00000001: # Particles have practically collided. Notice later that fe=0.0 ahead. | |
| r_2[ind_r][ind_c] = 0.0 | |
| if ind_r != ind_c: | |
| colisioned_part.append(ind_c) | |
| else: | |
| r_2[ind_r][ind_c] = value_sum | |
| colisioned_part_ = np.unique(np.array(colisioned_part)) | |
| # Each particle is assigned an electric charge magnitude based on the solution provided by the particle. | |
| q=np.array(q) | |
| min_value = q[0] | |
| max_value = q[-1] | |
| if minimize: | |
| q = -1.0*(q-max_value-1.0) | |
| max_value = q[0] | |
| if weight_method=="Linear": | |
| # We adjust the charges according to the following range [0, max_q]. | |
| q = (q/max_value)*max_q | |
| reverse_ind = [len(q)-i-1 for i in range(len(q))] # It is inverted to give more charge to the particles that are further away. | |
| q = q[reverse_ind] | |
| elif weight_method=="Quadratic": | |
| # We adjust the charges according to the following range [0, max_q]. | |
| q_2=q**2 | |
| q=(q_2/np.max(q_2))*max_q | |
| reverse_ind = [len(q)-i-1 for i in range(len(q))] # It is inverted to give more charge to the particles that are further away. | |
| q = q[reverse_ind] | |
| elif weight_method=="Exponential": | |
| # We adjust the charges according to the following range [0, max_q]. | |
| q_exp=np.array([np.exp(i) for i in q]) | |
| q=(q_exp/np.max(q_exp))*max_q | |
| reverse_ind = [len(q)-i-1 for i in range(len(q))] # It is inverted to give more charge to the particles that are further away. | |
| q = q[reverse_ind] | |
| Npar=d.shape[0] | |
| Nvar=d.shape[2] | |
| F=np.full((Npar, Npar, Nvar), 0.0) | |
| for ind_r, row in enumerate(dis): | |
| for ind_c, col in enumerate(row): | |
| # The magnitude of the electric force Fe is calculated. | |
| d_2=r_2[ind_r][ind_c] | |
| if d_2==0: | |
| Fe=0.0 | |
| else: | |
| q1=q[ind_r] | |
| q2=q[ind_c] | |
| Fe=float(k*q1*q2/d_2) | |
| if ind_r >= P and ind_c >= P: # Repulsive forces are generated between particles of the same sign. | |
| F[ind_r][ind_c]=-1.0*Fe*d[ind_r][ind_c] | |
| else: # There is attraction between particles and electric fields. | |
| F[ind_r][ind_c]=Fe*d[ind_r][ind_c] | |
| F[:P, :P] = 0.0 | |
| F_total = np.sum(F, axis=1) | |
| F_total[:P]=0.0 | |
| # Remember that F=ma -> m1*a1 = m2*a2. So, if m1>m2 -> a2>a1 -> Particles with greater mass have less acceleration. | |
| # For this method, the weight of the particle is not important, so we will set them all to be equal to 1.0. | |
| m=np.ones(Npar) | |
| # vf = acc + vi*t | |
| Ac=acc(F_total, m) | |
| vf = Ac + t*v | |
| Ac[:P]=0.0 | |
| vf[:P]=0.0 # The velocity of the magnetic fields is set to 0.0. | |
| # Finally, Xf = xi + vi*t + 0.5*acc*(t^2) (Final Position) | |
| x__=v*t + 0.5*Ac*(t**2) | |
| new_pos = x + x__ | |
| # If more than half of the particles have collided, those that have collided will move randomly within the reduced search space. | |
| max_crash_part=Npar/2 | |
| if len(colisioned_part_)>max_crash_part: | |
| lo = np.min(new_pos, axis=0) | |
| up = np.max(new_pos, axis=0) | |
| for i in colisioned_part_: | |
| new_pos[i] = np.random.uniform(low=lo, high=up, size=(1, Nvar)) | |
| return F_total, Ac, vf, new_pos | |