Spaces:
Runtime error
Runtime error
File size: 7,990 Bytes
7aab566 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
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
|