Spaces:
Runtime error
Runtime error
| from custom_pso import * | |
| import streamlit as st | |
| st.markdown(''' | |
| -------------------------------------------------------------- | |
| # Custom PSO algorithm for continuous domains | |
| Autor: Rodrigo Araya | |
| Email: [email protected] | |
| ''') | |
| st.markdown("------") | |
| st.markdown("### 1. Explicación Algoritmo") | |
| with st.expander("Precedimiento General"): | |
| st.markdown(''' | |
| 1. **Generar un archivo de tamaño** `nSize=nPart`. | |
| 2. **Generar** `nPart` **soluciones iniciales**. Cada solución contiene `nVar` variables. | |
| 3. **Evaluar las soluciones** `nPart` **y agregarlas al archivo** ordenadas de la mejor a la peor solución. | |
| 4. **Mientras no se cumpla la condición de terminación** (iteraciones > maxiter): | |
| 4.1. **Generar nuevas soluciones** utilizando algún solucionador. Las opciones son: ['Random', 'Newton', 'Coulomb']. | |
| 4.2. **Agregar las nuevas soluciones al archivo**. | |
| 4.3. **Eliminar soluciones duplicadas**. | |
| 4.4. **Evaluar las soluciones y ordenarlas** de la mejor a la peor solución. | |
| 4.5. **Mantener en el archivo las mejores** `nPar` **soluciones**. | |
| 4.6. **Guardar los resultados de la iteración en un historial** (dataframe). | |
| 4.7. **Evaluar la condición de terminación**. Si es negativa, regresar al paso 4.1. | |
| ''') | |
| st.image('Custom_PSO_workflow.png') | |
| st.markdown("### 2. Metodos") | |
| with st.expander("Random Method"): | |
| c1, c2 = st.columns(2) | |
| c1.markdown(''' | |
| Este solucionador genera una solución aleatoria para cada partícula. | |
| Además, también es posible aplicar un método de explotación donde el | |
| espacio de búsqueda se reduce en cada iteración. | |
| 1. Verificar `auto_reduce_search_space`. | |
| 2.1. Si `auto_reduce_search_space` es True. | |
| 2.1.1. Calcular límites `Lo` (lower bond) y `Up` (upper bond) basados en `x_`(soluciones inciales) | |
| 2.2. Si `auto_reduce_search_space` es `False`. | |
| 2.2.1. Establecer límites `Lo` y `Up` en cero y uno respectivamente | |
| 3. Generar nuevas posiciones aleatorias dentro de los límites | |
| 4. Ajustar las posiciones si exceden los límites | |
| 5. Evaluar las nuevas posiciones utilizando | |
| 6. Devolver las nuevas posiciones y evaluaciones | |
| ''') | |
| c2.image('Random.jpg') | |
| with st.expander("Newton Method"): | |
| c1, c2 = st.columns(2) | |
| c1.markdown(r''' | |
| El procedimiento que genera las nuevas posiciones de las partículas | |
| se basa en cómo los cuerpos se atraen entre sí a través de una fuerza | |
| de atracción (ley universal de la gravitación). Por lo tanto, el | |
| procedimiento será el siguiente: | |
| 1. Se reciben $n$ partículas en posiciones aleatorias dentro del | |
| espacio de búsqueda. | |
| 2. A cada partícula se le asigna una masa basada en la solución | |
| proporcionada por la partícula. | |
| - Cuanto mejor sea la solución proporcionada por la partícula, | |
| mayor será la masa asignada. | |
| - Métodos de asignación: Lineal, Exponencial, Cuadrática (se puede | |
| explorar una discusión adicional sobre mejores métodos de asignación). | |
| - En términos simples, se establece un rango de soluciones y se | |
| ajusta a un rango de masa entre 0 y `max_mass` según algún método de | |
| asignación. | |
| 3. Sea $F_{ij}$ la fuerza producida entre la partícula $i$ y $j$ -> | |
| $F_{ij} = \frac{G \cdot m_i \cdot m_j}{r^2}$ donde $r$ es la distancia | |
| entre las partículas, $m_i$ y $m_j$ son las masas de $i$ y $j$. | |
| - Por lo tanto, la fuerza resultante sobre la partícula $i$ es | |
| $FR_{i} = \sum_{j=1}^{n} \frac{G \cdot m_i \cdot m_j}{r_{ij}^2}$. | |
| - Además, por la Ley de Newton, $F = m \cdot a$ -> $a = \frac{F}{m}$. Por lo tanto, las velocidades de cada partícula se calculan como $v_f = v_i + a \cdot t$ -> $v_f = v_i + \frac{F}{m} \cdot t$. | |
| - Finalmente, las posiciones se actualizan de la siguiente manera: $x' = x + v_i \cdot t + \frac{a \cdot t^2}{2}$ -> $x' = x + v_i \cdot t + \frac{F}{m} \cdot \frac{t^2}{2}$. | |
| - Aplicando la suposición $v_i=0$ -> $x' = x + \frac{F}{m} \cdot \frac{t^2}{2}$. | |
| ''') | |
| c2.image('Newton.jpg') | |
| st.markdown(r''' | |
| Finalmente, si las partículas están demasiado cerca, podría causar un | |
| problema. Si $r_{ij}^{2}$ tiende a cero, entonces la fuerza de atracción sería | |
| demasiado fuerte, llevando a las partículas a separarse abruptamente | |
| entre sí. Para evitar este comportamiento, se establece una pequeña | |
| distancia `0.00000001`. Si la distancia entre dos partículas es menor | |
| que esta distancia, se considerará que ambas partículas han colisionado. | |
| Por último, si más de la mitad de las partículas han colisionado, se | |
| aplicará una función aleatoria para generar nuevas soluciones sobre | |
| un espacio de búsqueda reducido. | |
| ''') | |
| with st.expander("Coulomb Method"): | |
| c1, c2 = st.columns(2) | |
| c1.markdown(r''' | |
| 1. Se reciben n partículas en posiciones diferentes dentro del espacio | |
| de búsqueda. | |
| 2. A cada partícula se le asigna un tipo de carga positiva. | |
| 3. La magnitud de la carga de cada partícula está directamente relacionada | |
| con la función objetivo. | |
| - Métodos de Asignación: Lineal, Exponencial, Cuadrática (se pueden | |
| explorar métodos de asignación mejores) | |
| - En resumen, se establece un rango de solución y se ajusta a un | |
| rango de carga entre 0 y `max_q` según algún método de asignación. | |
| 4. Cada partícula tiene una velocidad. Estas comienzan en reposo pero | |
| cambiarán a lo largo de las iteraciones del algoritmo. | |
| 5. Las `P` partículas con los mejores valores obtenidos de la iteración | |
| permanecerán en reposo y emitirán un campo eléctrico de signo opuesto | |
| (negativo) atrayendo al resto. | |
| - Sea $E = \frac{kQ}{r^2}$ el campo magnético en un punto ubicado a | |
| una distancia $r$ de la fuente. | |
| - Sea $Fe$ la fuerza eléctrica entre el campo magnético y la partícula | |
| -> $Fe = E*q_{0}$ donde $q_{0}$ es la carga de la partícula. | |
| - Sea $F_{ij}$ la fuerza producida entre la partícula $i$ y $j$ -> | |
| $F_{ij} = \frac{k*q_{i}*q_{j}}{r^2}$ donde $r$ es la distancia | |
| entre las partículas, $q_{i}$ y $q_{j}$ son las cargas de $i$ y $j$. | |
| - Por lo tanto, las fuerzas resultantes de la partícula $i$ son | |
| $FR_{i} = \sum_{p=1}^{P} \frac{k*Q_{p}*Q_{i}}{r_{ip}^{2}} + \sum_{j=P+1}^{n} \frac{k*Q_{i}*Q_{j}}{r_{ij}^{2}}$ | |
| - Además, por la Ley de Newton, $F=m*a$ -> $Fe=m*a$. Por lo tanto, las | |
| velocidades de cada partícula se calculan $v_{f}=v_{i}+a*t$ -> $v_{f} = v_{i} + (\frac{FR_{i}}{m})*t$ | |
| - Las posiciones se actualizan de la siguiente manera | |
| $x = v_{i}*t + \frac{(a*t^{2})}{2}$ -> $x = v_{i}*t + (\frac{FR_{i}}{m})*t^{2}*0.5$ | |
| ''') | |
| c2.image('Coulomb.jpg') | |
| st.markdown(r''' | |
| Finalmente, si las partículas están demasiado cerca, podría causar un | |
| problema. Si $r_{ij}^{2}$ tiende a cero, entonces la fuerza eléctrica sería | |
| demasiado fuerte, llevando a las partículas a separarse abruptamente | |
| entre sí. Para evitar este comportamiento, se establece una pequeña | |
| distancia `0.00000001`. Si la distancia entre dos partículas es menor | |
| que esta distancia, se considerará que ambas partículas han colisionado. | |
| Por último, si más de la mitad de las partículas han colisionado, se | |
| aplicará una función aleatoria para generar nuevas soluciones sobre | |
| un espacio de búsqueda reducido. | |
| ''') | |
| st.markdown("### 3. Codigos") | |
| with st.expander("F_newton.py"): | |
| st.markdown(r''' | |
| ```python | |
| 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] | |
| # Matriz de aceleracion | |
| 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_newton(x_, m, minimize=True, G=0.0001, keep_best=False, weight_method='Linear', t=1.0, max_mass=100.0): | |
| """ The procedure that generates the new positions of the particles is based on how bodies are attracted to each other | |
| through an attractive force (universal law of gravitation). 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 mass based on the solution provided by the particle. | |
| 2.1 The better the solution provided by the particle, the greater the assigned mass. | |
| 2.2 Assignment methods -> Linear, Exponential, Quadratic (further discussion on better assignment methods can be explored). | |
| 2.2.1 In simple terms, a range of solutions is established and adjusted to a charge range between 0 to max_mass according to some assignment method. | |
| 3. Let Fij be the force produced between particle i and j -> Fij = G*mi*mj/(r^2) where r is the distance between the particles, mi and mj are the masses of i and j. | |
| 3.1 Thus, the resulting force on particle i is FRi = Σ{j=1} G*mi*mj/(rij^2). | |
| 3.2 Additionally, by Newton's Law, F = m*a -> a = F/m. Thus, the velocities of each particle are calculated as vf = vi + a*t -> vf = vi + (F/m)*t. | |
| 3.3 Finally, the positions are updated as follows: x' = x + vi*t + (a*t^2)/2 -> x' = x + vi*t + (F/m)*(t^2)/2. | |
| 3.4 Applying the assumption vi=0 -> x' = x + (F/m)*(t^2)/2. | |
| Args: | |
| x_ (array(size=(Npar, Nvar)): current particle positions | |
| m (array(size=(Npar, 1))): current mass of each particle | |
| minimize (bool, optional): solver objective. Defaults to True. | |
| G (float, optional): gravitational constant. Defaults to 0.0001. | |
| keep_best (bool, optional): It will save the best value obtained in each iteration. Defaults to False. | |
| weight_method (str, optional): method to reassign mass. Defaults to 'Linear'. Options=['Linear', 'Quadratic', 'Exponential']. | |
| t (float, optional): time. Defaults to 1.0. | |
| max_mass (float, optional): upper bound of mass to assing. Defaults to 100.0. | |
| Returns: | |
| F_total, Ac, new_pos: returns the force obtained on each particle, their acceleration, | |
| 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 F_=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 a mass magnitude based on the solution provided by the particle. | |
| m=np.array(m) | |
| min_value = m[0] | |
| max_value = m[-1] | |
| if minimize: | |
| m = -1.0*(m-max_value-1.0) | |
| max_value = m[0] | |
| if weight_method=="Linear": | |
| # We adjust the mass according to the following range [0, max_mass]. | |
| m = (m/max_value)*max_mass | |
| reverse_ind = [len(m)-i-1 for i in range(len(m))] | |
| m = m[reverse_ind] | |
| elif weight_method=="Quadratic": | |
| # We adjust the mass according to the following range [0, max_mass]. | |
| m_2=m**2 | |
| m=(m_2/np.max(m_2))*max_mass | |
| reverse_ind = [len(m)-i-1 for i in range(len(m))] | |
| m = m[reverse_ind] | |
| elif weight_method=="Exponential": | |
| # We adjust the mass according to the following range [0, max_mass]. | |
| m_exp=np.array([np.exp(i) for i in m]) | |
| m=(m_exp/np.max(m_exp))*max_mass | |
| reverse_ind = [len(m)-i-1 for i in range(len(m))] | |
| m = m[reverse_ind] | |
| m = np.nan_to_num(m, nan=0.0001) | |
| 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 attraction force F_ is calculated. | |
| d_2=r_2[ind_r][ind_c] | |
| if d_2==0: | |
| F_=0.0 | |
| else: | |
| m1=m[ind_r] | |
| m2=m[ind_c] | |
| F_=float(G*m1*m2/d_2) | |
| F[ind_r][ind_c]=F_*d[ind_r][ind_c] | |
| F_total = np.sum(F, axis=1) | |
| # Note: Between two particles, the attractive forces will be the same, but not their accelerations. | |
| # Remember that F = M * A -> m1 * a1 = m2 * a2, so if m1 > m2 -> a2 > a1 -> Particles with greater mass have lower acceleration.Ac=acc(F_total, m) | |
| Ac=acc(F_total, m) | |
| Ac = np.nan_to_num(Ac, nan=0.0) | |
| # Finally, Xf = xi + vi * t + 0.5 * acc * (t^2) (Final Position), but if the particles always start at rest (vi=0) -> xf = 0.5 * acc * (t^2). | |
| x__ = 0.5*Ac*(t**2) | |
| new_pos=x_ + x__ | |
| if keep_best==True: | |
| best_ind = np.argmin(m) | |
| Ac[best_ind]=0.0 | |
| new_pos=x_ + 0.5*Ac*(t**2) | |
| # 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, new_pos | |
| ```''') | |
| with st.expander('F_coulomb.py'): | |
| st.markdown(''' | |
| ```python | |
| 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 | |
| ``` | |
| ''') | |
| with st.expander("Custom_pso.py"): | |
| st.markdown(''' | |
| ```python | |
| import math | |
| import numpy as np | |
| import pandas as pd | |
| from F_newton import F_newton | |
| from F_coulomb import F_coulomb | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| def Random_(problem, x_, auto_reduce_search_space=False): | |
| """ This solver generates a random solution for each particle. Additionally, it is also possible to | |
| apply an exploitation method where the search space is reduced on each iteration. | |
| Args: | |
| problem (dic): Dictionary that include: objective function, lower bound and upper bound of each variable and optimal solution | |
| x_ (array(size=())): Current position of each particle | |
| auto_reduce_search_space (bool, optional): If True, it update the lower and upper bounds in a way to reduce the search space. Defaults to False. | |
| Returns: | |
| Stemp, S_f: New positions for each particle and their performance after being evaluated with the objective function. | |
| """ | |
| nPart = x_.shape[0] | |
| nVar = x_.shape[1] | |
| if auto_reduce_search_space: | |
| Lo = np.min(x_, axis=0) | |
| Up = np.max(x_, axis=0) | |
| else: | |
| Lo = np.zeros(nVar) | |
| Up = np.ones(nVar) | |
| new_x = np.random.uniform(low=Lo, high=Up, size=(nPart, nVar)) | |
| Stemp = np.zeros((nPart,nVar)) | |
| for k in range(nPart): | |
| for i in range(nVar): | |
| Stemp[k][i] = new_x[k][i] | |
| if Stemp[k][i] > Up[i]: | |
| Stemp[k][i] = Up[i] | |
| elif Stemp[k][i] < Lo[i]: | |
| Stemp[k][i] = Lo[i] | |
| f,S_r,maximize = mp_evaluator(problem, Stemp) | |
| S_f = np.zeros((nPart,1)) | |
| for i in range(len(S_r)): | |
| S_f[i] = f[i] | |
| return Stemp, S_f | |
| def F_newton_(problem, x_, m, Lo, Up, G=0.00001, keep_best=True, weight_method='Linear', t=1.0, max_mass=100.0): | |
| _, _, new_x = F_newton(x_, m, minimize=True, G=G, keep_best=keep_best, weight_method=weight_method, t=t, max_mass=max_mass) | |
| nPart=x_.shape[0] | |
| nVar=x_.shape[1] | |
| Stemp = np.zeros((nPart,nVar)) | |
| for k in range(nPart): | |
| for i in range(nVar): | |
| Stemp[k][i] = new_x[k][i] | |
| if Stemp[k][i] > Up[i]: | |
| Stemp[k][i] = Up[i] | |
| elif Stemp[k][i] < Lo[i]: | |
| Stemp[k][i] = Lo[i] | |
| f,S_r,maximize = mp_evaluator(problem, Stemp) | |
| S_f = np.zeros((nPart,1)) | |
| for i in range(len(S_r)): | |
| S_f[i] = f[i] | |
| return Stemp, S_f | |
| def F_coulomb_(problem, x_, v, q, P, Lo, Up, k=0.00001, weight_method='Linear', t=1.0, max_q=100.0): | |
| _, _, v, new_x = F_coulomb(x_, v, q, P=P, minimize=True, k=k, weight_method=weight_method, t=t, max_q=max_q) | |
| nPart=x_.shape[0] | |
| nVar=x_.shape[1] | |
| Stemp = np.zeros((nPart,nVar)) | |
| for k in range(nPart): | |
| for i in range(nVar): | |
| Stemp[k][i] = new_x[k][i] | |
| if Stemp[k][i] > Up[i]: | |
| Stemp[k][i] = Up[i] | |
| elif Stemp[k][i] < Lo[i]: | |
| Stemp[k][i] = Lo[i] | |
| f,S_r,maximize = mp_evaluator(problem, Stemp) | |
| S_f = np.zeros((nPart,1)) | |
| for i in range(len(S_r)): | |
| S_f[i] = f[i] | |
| return Stemp, v, S_f | |
| def evaluator(problem, x): | |
| # Dado que se trabaja bajo el dominio [0, 1] para cada variable, se debe crear una funcion que regrese a los valores a los dominios originales | |
| # Ejemplo (ndim=2): si se tiene el dominio original [-5, 5] y [-2, 2] para la variables x1, x2 y se cambio a [0, 1] y [0, 1] se vuelve al original tras: | |
| # y1=ax+b -> -5 = a*0+b -> b=-5 | |
| # y1=ax+b -> 5 = a*1+-5 -> a=10 | |
| # y1=10*x+-5 | |
| # y2=ax+b -> -2 = a*0+b -> b=-2 | |
| # y2=ax+b -> 2 = a*1+-2 -> a=4 | |
| # y2=4*x+-2 | |
| # Luego se aplica y1(x1) e y2(x2) respectivamente. Extendible a n dimensiones. | |
| # Dado que [0, 1] no cambia, se generaliza la formula b=lb y a=ub-lb -> y_{i} = (ub-lb)_{i}*x + lb_{i} | |
| lb = [var[0] for var in problem['bounds']] | |
| ub = [var[1] for var in problem['bounds']] | |
| x = [(ub[ind]-lb[ind])*i+lb[ind] for ind, i in enumerate(x)] | |
| # calculate fitness | |
| f = problem['f'](x) | |
| fitness = dict(Obj=f) | |
| return fitness | |
| def mp_evaluator(problem, x): | |
| results = [evaluator(problem, c) for c in x] | |
| f = [r['Obj'] for r in results] | |
| # maximization or minimization problem | |
| maximize = False | |
| return (f, [r for r in results],maximize) | |
| def correct_x(problem, x_): | |
| lb = [var[0] for var in problem['bounds']] | |
| ub = [var[1] for var in problem['bounds']] | |
| x = np.empty(x_.shape) | |
| for ind, row in enumerate(x_): | |
| corr_row = np.array([(ub[ind]-lb[ind])*i+lb[ind] for ind, i in enumerate(row)]) | |
| x[ind]=corr_row | |
| return x | |
| def custom_pso(problem, nPart, nVar, maxiter, G=0.00001, k=0.0001, keep_best=True, weight_method='Linear', t=1.0, seed=0, max_mass=100.0, solver='Newton', P=3, max_q=100.0, auto_reduce_search_space=False, dinamic=False): | |
| """The procedure of this algorithm is as follows: | |
| 1. Generate a file of size nSize=nPart. | |
| 2. Generate nPart initial solutions. Each solution contains nVar variables. | |
| 3. Evaluate the nPart solutions and add them to the file ordered from best to worst solution. | |
| 4. While the termination condition is not met (iterations > maxiter): | |
| 4.1 Generate new solutions using some solver. Options = ['Random', 'Newton', 'Coulomb'] | |
| 4.2 Add new solutions to the file. | |
| 4.3 Remove duplicate solutions. | |
| 4.4 Evaluate solutions and sort from best to worst solution. | |
| 4.5 Keep in the file the nPar best solutions. | |
| 4.6 Save iteration results in a history (dataframe). | |
| 4.7 Evaluate termination condition. If negative, return to step 4.1. | |
| Args: | |
| problem (dic): Dictionary that include: objective function, lower bound and upper bound of each variable and optimal solution | |
| nPart (_type_): Quantity of particles | |
| nVar (_type_): Quantity of variables | |
| maxiter (_type_): Maximum number of iterations. | |
| seed (int, optional): set the generation of random numbers. Defaults to 0. | |
| solver (str, optional): solver to apply. Defaults to 'Newton'. Options=['Random', 'Newton', 'Coulomb']. | |
| Random Solver: | |
| auto_reduce_search_space (bool, optional): If True, it update the lower and upper bounds in a way to reduce the search space. Defaults to False. | |
| Newton Solver: | |
| G (float, optional): Gravitational constant. Defaults to 0.00001. | |
| keep_best (bool, optional): It will keep the best value obtained in each iteration. Defaults to True. | |
| weight_method (str, optional): method to reassign particle mass. Defaults to 'Linear'. Options=['Linear', 'Quadratic', 'Exponential']. | |
| t (float, optional): time. Defaults to 1.0. | |
| max_mass (float, optional): upper bound of mass to assing. Defaults to 100.0. | |
| dinamic (bool, optional): It will change the max_mass value depending on the current iteration and difference between best and worst value obteined. Defaults to False. | |
| Coulomb Solver: | |
| P (int): quantity of electric fields (the best P particles become electric fields) | |
| max_q (float, optional): upper bound of electric charge to assing. Defaults to 100.0. | |
| weight_method (str, optional): method to reassign electric charges. Defaults to 'Linear'. Options=['Linear', 'Quadratic', 'Exponential']. | |
| k (float, optional): electric constant. Defaults to 0.0001. | |
| t (float, optional): time. Defaults to 1.0. | |
| dinamic (bool, optional): It will change the max_q value depending on the current iteration and difference between best and worst value obteined. Defaults to False. | |
| Returns: | |
| df, best_var, best_sol: dataframe contening data from all iterations, the best variables and the best value obtained | |
| """ | |
| # number of variables | |
| parameters_v = [f'x{i+1}' for i in range(nVar)] | |
| # number of variables | |
| nVar = len(parameters_v) | |
| # size of solution archive | |
| nSize = nPart | |
| # number of Particules | |
| nPart = nPart | |
| # maximum iterations | |
| maxiter = maxiter | |
| # bounds of variables | |
| Up = [1]*nVar | |
| Lo = [0]*nVar | |
| # dinamic q | |
| din_max_q=max_q | |
| # dinamic mass | |
| din_max_mass = max_mass | |
| # initilize matrices | |
| S = np.zeros((nSize,nVar)) | |
| S_f = np.zeros((nSize,1)) | |
| # initial velocity | |
| v = np.zeros((nSize,nVar)) | |
| # history | |
| columns_ = ['iter'] | |
| for i in parameters_v: columns_.append(i) | |
| columns_.append('f') | |
| df = pd.DataFrame(columns=columns_) | |
| # generate first random solution | |
| #np.random.seed(seed) | |
| Srand = np.random.uniform(low=0,high=1,size=(nPart, nVar)) | |
| f,S_r,maximize = mp_evaluator(problem, Srand) | |
| for i in range(len(S_r)): | |
| S_f[i] = f[i] | |
| # add responses and "fitness" column to solution | |
| S = np.hstack((Srand, v, S_f)) | |
| # sort according to fitness (last column) | |
| S = sorted(S, key=lambda row: row[-1],reverse = maximize) | |
| S = np.array(S) | |
| # save each iteration | |
| iter_n = np.full((nPart, 1), 0.0) | |
| x_ = S[:, 0:nVar] | |
| x = correct_x(problem, x_) | |
| res = np.reshape(S[:, -1], (nPart, 1)) | |
| rows=np.hstack((iter_n, x, res)) | |
| df_new = pd.DataFrame(rows, columns=columns_) | |
| df = pd.concat([df, df_new]) | |
| iterations=1 | |
| # iterations | |
| while True: | |
| # get a new solution | |
| if solver == "Random": | |
| Stemp, S_f = Random_(problem, S[:, :nVar], auto_reduce_search_space=auto_reduce_search_space) | |
| elif solver=="Newton": | |
| din_mass=0 | |
| if dinamic: | |
| din_mass = ((maxiter-iterations)/(maxiter))*(np.max(S_f)-np.min(S_f)) | |
| else: | |
| din_mass=max_mass | |
| m = np.reshape(S[:, -1], (nPart, 1)) | |
| Stemp, S_f = F_newton_(problem, S[:, :nVar], m, Lo, Up, G=G, keep_best=keep_best, weight_method=weight_method, t=t, max_mass=din_mass) | |
| elif solver == "Coulomb": | |
| din_q=0 | |
| if dinamic: | |
| din_q = ((maxiter-iterations)/(maxiter))*(np.max(S_f)-np.min(S_f)) | |
| else: | |
| din_q=max_q | |
| q = np.reshape(S[:, -1], (nPart, 1)) | |
| Stemp, v, S_f = F_coulomb_(problem, S[:, :nVar], v, q, P, Lo, Up, k=k, weight_method=weight_method, t=t, max_q=din_q) | |
| # add responses and "fitness" column to solution | |
| Ssample = np.hstack((Stemp, v, S_f)) | |
| # add new solutions in the solutions table | |
| Solution_temp = np.vstack((S,Ssample)) | |
| # delate duplicated rows | |
| Solution_temp = np.unique(Solution_temp, axis=0) | |
| # sort according to "fitness" | |
| Solution_temp = sorted(Solution_temp, key=lambda row: row[-1],reverse = maximize) | |
| Solution_temp = np.array(Solution_temp) | |
| # keep best solutions | |
| S = Solution_temp[:nSize][:] | |
| # save each iteration | |
| iter_n = np.full((nPart, 1), iterations) | |
| x_ = S[:, 0:nVar] | |
| x = correct_x(problem, x_) | |
| res = np.reshape(S[:, -1], (nPart, 1)) | |
| rows=np.hstack((iter_n, x, res)) | |
| df_new = pd.DataFrame(rows, columns=columns_) | |
| df = pd.concat([df, df_new]) | |
| iterations += 1 | |
| if iterations > maxiter: | |
| break | |
| best_sol = np.min(df['f']) | |
| ind_best_sol = np.argmin(df['f']) | |
| best_var = df.iloc[ind_best_sol, 1:len(parameters_v)+1] | |
| return df, best_var, best_sol | |
| # Test Functions. | |
| # Adapted from "https://www.sfu.ca/~ssurjano/optimization.html" | |
| def Ackley(x, a=20.0, b=0.2, c=2.0*np.pi): | |
| d = len(x) | |
| sum1 = np.sum(np.square(x)) | |
| sum2 = np.sum(np.array([np.cos(c*i) for i in x])) | |
| term1 = -a * np.exp(-b * np.sqrt(sum1 / d)) | |
| term2 = -np.exp(sum2 / d) | |
| return term1 + term2 + a + np.exp(1) | |
| def sixth_Bukin(x): | |
| return 100 * np.sqrt(np.abs(x[1] - 0.01*x[0]**2)) + 0.01*np.abs(x[0] + 10) | |
| def Cross_in_Tray(x): | |
| return -0.0001 * math.pow(np.abs(np.sin(x[0]) * np.sin(x[1]) * np.exp(np.abs(100.0 - np.sqrt(x[0]**2 + x[1]**2)/np.pi)))+1.0, 0.1) | |
| def Drop_Wave(x): | |
| return -1.0*(1.0 + np.cos(12.0 * np.sqrt(x[0]**2 + x[1]**2))) / (0.5 * (x[0]**2 + x[1]**2) + 2.0) | |
| def Eggholder(x): | |
| return -(x[1] + 47.0) * np.sin(np.sqrt(np.abs(x[1] + x[0]/2 + 47.0))) - x[0] * np.sin(np.sqrt(np.abs(x[0] - (x[1] + 47.0)))) | |
| def Griewank(x): | |
| sum_part=0.0 | |
| prod_part=1.0 | |
| for i, xi in enumerate(x): | |
| sum_part += xi/4000.0 | |
| prod_part *= np.cos(xi/np.sqrt(i+1)) | |
| return sum_part - prod_part + 1.0 | |
| def Holder_Table(x): | |
| return -np.abs(np.sin(x[0])*np.cos(x[1])*np.exp(np.abs(1.0-(np.sqrt(x[0]**2 + x[1]**2)/np.pi)))) | |
| def Levy(x): | |
| # d dimensions | |
| w1 = 1.0 + (x[0]-1.0)/4.0 | |
| wd = 1.0 + (x[-1]-1.0)/4.0 | |
| sum_part = 0.0 | |
| for i, xi in enumerate(x): | |
| wi = 1.0 + (xi-1.0)/4.0 | |
| sum_part += ((wi-1.0)**2)*(1.0 + 10.0*math.pow(np.sin(np.pi*wi+1.0), 2)) | |
| return math.pow(np.sin(np.pi*w1), 2) + sum_part + ((wd-1.0)**2)*(1.0 + math.pow(np.sin(2*np.pi*wd), 2)) | |
| def Rastrigin(x): | |
| # d dimensions | |
| d = len(x) | |
| sum_part = 0.0 | |
| for i, xi in enumerate(x): | |
| sum_part += (xi**2) - 10.0*np.cos(2*np.pi*xi) | |
| return 10*d + sum_part | |
| def second_Schaffe(x): | |
| return 0.5 + (math.pow(np.sin(x[0]**2 + x[1]**2), 2)-0.5)/(1.0 + 0.001*(x[0]**2 + x[1]**2))**2 | |
| def fourth_Schaffer(x): | |
| return 0.5 + (math.pow(np.cos(np.abs(x[0]**2 - x[1]**2)), 2)-0.5)/(1.0 + 0.001*(x[0]**2 + x[1]**2))**2 | |
| def Schwefel(x): | |
| # d dimensions | |
| d = len(x) | |
| sum_part = 0.0 | |
| for xi in x: | |
| sum_part += xi*np.sin(np.sqrt(np.abs(xi))) | |
| return 418.9829*d - sum_part | |
| def Shubert(x): | |
| sum_1 = 0.0 | |
| sum_2 = 0.0 | |
| for i in np.arange(5): | |
| i = float(i + 1) | |
| sum_1 += i*np.cos((i+1)*x[0] + i) | |
| sum_2 += i*np.cos((i+1)*x[1] + i) | |
| return sum_1*sum_2 | |
| def Styblinski_Tang(x): | |
| f = (sum([math.pow(i,4)-16*math.pow(i,2)+5*i for i in x])/2) | |
| return f | |
| def Easom(x): | |
| f = np.cos(x[0])*np.cos(x[1])*np.exp(-(math.pow(x[0]-np.pi, 2) + math.pow(x[1]-np.pi, 2))) | |
| return f | |
| def Bohachevsky(x): | |
| f = x[0]**2 + 2*x[1]**2 -0.3*np.cos(3*np.pi*x[0]) - 0.4*np.cos(4*np.pi*x[1]) + 0.7 | |
| return f | |
| def Perm_d_beta(x, d=2, beta=4): | |
| f = 0.0 | |
| for i in range(d): | |
| for j in range(d): | |
| f += ((j+1) + beta)*(x[j]**(i+1) - (1/(j+1)**(i+1)))**2 | |
| return f | |
| def Rotated_Hyper_Ellipsoid(x, d=2): | |
| f = 0.0 | |
| for i in range(d): | |
| for j in range(i+1): | |
| f += x[j]**2 | |
| return f | |
| def Sum_of_Different_Powers(x, d=2): | |
| f = 0.0 | |
| for i in range(d): | |
| f += np.abs(x[i])**i+1+1 | |
| return f | |
| def SUM_SQUARES(x, d=2): | |
| f = 0.0 | |
| for i in range(d): | |
| f += (i+1)*x[i]**2 | |
| return f | |
| def TRID(x, d=2): | |
| sum_1 = 0.0 | |
| sum_2 = 0.0 | |
| for i in range(d): | |
| sum_1 += (x[i] - 1)**2 | |
| for i in range(d-1): | |
| sum_2 += x[i+1]*x[i] | |
| f = sum_1 + sum_2 | |
| return f | |
| def BOOTH(x): | |
| f = (x[0] + 2*x[1] -7)**2 + (2*x[0] + x[1] - 5)**2 | |
| return f | |
| def Matyas(x): | |
| f = 0.26*(x[0]**2 + x[1]**2) - 0.48*x[0]*x[1] | |
| return f | |
| def MCCORMICK(x): | |
| f = np.sin(x[0] + x[1]) + (x[0] - x[1])**2 - 1.5*x[0] + 2.5*x[1] + 1 | |
| return f | |
| def Power_Sum(x, d=2, b=[8, 18, 44, 114]): | |
| f = 0.0 | |
| for i in range(d): | |
| sum_1 = 0.0 | |
| for j in range(d): | |
| sum_1 += x[j]**(i+1) | |
| f += (sum_1 - b[i])**2 | |
| return f | |
| def Zakharov(x, d=2): | |
| f = 0.0 | |
| sum_1 = 0.0 | |
| sum_2 = 0.0 | |
| sum_3 = 0.0 | |
| for i in range(d): | |
| sum_1 += x[i]**2 | |
| sum_2 += 0.5*(i+1)*x[i] | |
| sum_3 += 0.5*(i+1)*x[i] | |
| f = sum_1 + sum_2**2 + sum_3**4 | |
| return f | |
| def THREE_HUMP_CAMEL(x): | |
| f = 2*x[0]**2 - 1.05*x[0]**4 + (x[0]**6/6) + x[0]*x[1] + x[1]**2 | |
| return f | |
| def SIX_HUMP_CAMEL(x): | |
| f = (4 - 2.1*x[0] + (x[0]**4)/3)*x[0]**2 + x[0]*x[1] + (-4 + 4*x[1]**2)*x[1]**2 | |
| return f | |
| def DIXON_PRICE(x, d=2): | |
| sum_1 = 0.0 | |
| for i in range(d-1): | |
| i = i + 1 | |
| sum_1 += (i+1)*(2*x[i]**2 - x[i-1])**2 | |
| f = (x[0] - 1)**2 + sum_1 | |
| return f | |
| def ROSENBROCK(x, d=2): | |
| f = 0.0 | |
| for i in range(d-1): | |
| f += 100*(x[i+1] - x[i]**2)**2 + (x[i] - 1)**2 | |
| return f | |
| def DE_JONG(x): | |
| a = [[-32, -16, 0, 16, 32, -32, -16, 0, 16, 32, -32, -16, 0, 16, 32, -32, -16, 0, 16, 32, -32, -16, 0, 16, 32], | |
| [-32, -32, -32, -32, -32, -16, -16, -16, -16, -16, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32]] | |
| sum_1 = 0.0 | |
| for i in range(25): | |
| sum_1 += 1/((i+1) + (x[0] - a[0][i])**6 + (x[1] - a[1][i])**6) | |
| f = (0.002 + sum_1)**(-1) | |
| return f | |
| def MICHALEWICZ(x, d=2, m=10): | |
| f = 0.0 | |
| for i in range(d): | |
| f += np.sin(x[i])*np.sin(((i+1)*x[i]**2)/np.pi)**(2*m) | |
| f = -f | |
| return f | |
| def BEALE(x): | |
| f = (1.5 - x[0] + x[0]*x[1])**2 + (2.25 - x[0] + x[0]*x[1]**2)**2 + (2.625 - x[0] + x[0]*x[1]**3)**2 | |
| return f | |
| def BRANIN(x): | |
| a=1 | |
| b=5.1/(4*(np.pi)**2) | |
| c = 5/np.pi | |
| r = 6 | |
| s = 10 | |
| t = 1/(8*np.pi) | |
| f = a*(x[1] - b*x[0]**2 + c*x[0] - r)**2 + s*(1 - t)*np.cos(x[0]) + s | |
| return f | |
| def GOLDSTEIN_PRICE(x): | |
| f = (1 + ((x[0] + x[1] + 1)**2)*(19 - 14*x[0] + 3*x[0]**2 - 14*x[1] + 6*x[0]*x[1] + 3*x[1]**2))*(30 + ((2*x[0] - 3*x[1])**2)*(18 - 32*x[0] + 12*x[0]**2 + 48*x[1] - 36*x[0]*x[1] + 27*x[1]**2)) | |
| return f | |
| def PERM_D_BETA(x, d=2, beta=1): | |
| f = 0.0 | |
| for i in range(d): | |
| sum_1 = 0 | |
| for j in range(d): | |
| sum_1 += ((j+1)**(i+1) + beta)*((x[j]/(j+1))**(i+1) - 1) | |
| f += sum_1 | |
| return f | |
| class test_functions(): | |
| def __init__(self) -> None: | |
| self.Ackley_ = {'name':'Ackley', 'f':Ackley, 'bounds':[[-32.768, 32.768], [-32.768, 32.768]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.sixth_Bukin_ = {'name':'sixth_Bukin', 'f':sixth_Bukin, 'bounds':[[-15.0, -5.0], [-3.0, 3.0]], 'opt':[[-10.0, 1.0], 0.0]} | |
| self.Cross_in_Tray_ = {'name':'Cross_in_Tray', 'f':Cross_in_Tray, 'bounds':[[-10.0, 10.0], [-10.0, 10.0]], 'opt':[[[1.3491, -1.3491], [1.3491, 1.3491], [-1.3491, 1.3491], [-1.3491, -1.3491]], -2.06261]} | |
| self.Drop_Wave_ = {'name':'Drop_Wave', 'f':Drop_Wave, 'bounds':[[-5.12, 5.12], [-5.12, 5.12]], 'opt':[[0, 0], -1.0]} | |
| self.Eggholder_ = {'name':'Eggholder', 'f':Eggholder, 'bounds':[[-512.0, 512.0], [-512.0, 512.0]], 'opt':[[512.404, 512.404], -959.6407]} | |
| self.Griewank_ = {'name':'Griewank', 'f':Griewank, 'bounds':[[-600.0, 600.0], [-600.0, 600.0]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.Holder_Table_ = {'name':'Holder_Table', 'f':Holder_Table, 'bounds':[[-10.0, 10.0], [-10.0, 10.0]], 'opt':[[[8.05502, 9.66459], [8.05502, -9.66459], [-8.05502, 9.66459], [-8.05502, -9.66459]], -19.2085]} | |
| self.Levy_ = {'name':'Levy', 'f':Levy, 'bounds':[[-10.0, 10.0], [-10.0, 10.0]], 'opt':[[1.0, 1.0], 0.0]} | |
| self.Rastrigin_ = {'name':'Rastrigin', 'f':Rastrigin, 'bounds':[[-5.12, 5.12], [-5.12, 5.12]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.second_Schaffe_ = {'name':'second_Schaffe', 'f':second_Schaffe, 'bounds':[[-100.0, 100.0], [-100.0, 100.0]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.fourth_Schaffer_ = {'name':'fourth_Schaffer', 'f':fourth_Schaffer, 'bounds':[[-100.0, 100.0], [-100.0, 100.0]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.Schwefel_ = {'name':'Schwefel', 'f':Schwefel, 'bounds':[[-500.0, 500.0], [-500.0, 500.0]], 'opt':[[420.9687, 420.9687], 0.0]} | |
| self.Shubert_ = {'name':'Shubert', 'f':Shubert, 'bounds':[[-10.0, 10.0], [-10.0, 10.0]], 'opt':[[0.0, 0.0], -186.7309]} | |
| self.Styblinski_Tang_ = {'name':'Styblinski_Tang', 'f':Styblinski_Tang, 'bounds':[[-5, 5], [-5, 5]]} | |
| self.Easom_ = {'name':'Easom', 'f':Easom, 'bounds':[[-3, 3], [-3, 3]]} | |
| self.Bohachevsky_ = {'name':'Bohachevsky', 'f':Bohachevsky, 'bounds':[[-100.0, 100.0], [-100.0, 100.0]], 'opt':[[0, 0], 0]} | |
| self.Perm_d_beta_ = {'name':'Perm_d_beta', 'f':Perm_d_beta, 'bounds':[[-2.0, 2.0], [-2.0, 2.0]], 'opt':[[1, 0.5], 0]} | |
| self.Rotated_Hyper_Ellipsoid_ = {'name':'Rotated_Hyper_Ellipsoid', 'f':Rotated_Hyper_Ellipsoid, 'bounds':[[-65.536, 65.536], [-65.536, 65.536]], 'opt':[[0.0, 0.0], 0]} | |
| self.Sum_of_Different_Powers_ = {'name':'Sum_of_Different_Powers', 'f':Sum_of_Different_Powers, 'bounds':[[-1, 1], [-1, 1]], 'opt':[[0.0, 0.0], 0]} | |
| self.SUM_SQUARES_ = {'name':'SUM_SQUARES', 'f':SUM_SQUARES, 'bounds':[[-10, 10], [-10, 10]], 'opt':[[0.0, 0.0], 0]} | |
| self.TRID_ = {'name':'TRID', 'f':TRID, 'bounds':[[-4, 4], [-4, 4]], 'opt':[[2, 2], -2]} | |
| self.BOOTH_ = {'name':'BOOTH', 'f':BOOTH, 'bounds':[[-10, 10], [-10, 10]], 'opt':[[1, 3], 0]} | |
| self.Matyas_ = {'name':'Matyas', 'f':Matyas, 'bounds':[[-10, 10], [-10, 10]], 'opt':[[0, 0], 0]} | |
| self.MCCORMICK_ = {'name':'MCCORMICK', 'f':MCCORMICK, 'bounds':[[-1.5, 4], [-3, 4]], 'opt':[[-0.54719, -1.54719], -1.9133]} | |
| self.Power_Sum_ = {'name':'Power_Sum', 'f':Power_Sum, 'bounds':[[0, 2], [0, 2]]} | |
| self.Zakharov_ = {'name':'Zakharov', 'f':Zakharov, 'bounds':[[-5, 10], [-5, 10]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.THREE_HUMP_CAMEL_ = {'name':'THREE_HUMP_CAMEL', 'f':THREE_HUMP_CAMEL, 'bounds':[[-5, 5], [-5, 5]], 'opt':[[0.0, 0.0], 0.0]} | |
| self.SIX_HUMP_CAMEL_ = {'name':'SIX_HUMP_CAMEL', 'f':SIX_HUMP_CAMEL, 'bounds':[[-3, 3], [-2, 2]], 'opt':[[0.0898, -0.7126], -1.0316]} | |
| self.DIXON_PRICE_ = {'name':'DIXON_PRICE', 'f':DIXON_PRICE, 'bounds':[[-10, 10], [-10, 10]], 'opt':[[1, 1/np.sqrt(2)], 0]} | |
| self.ROSENBROCK_ = {'name':'ROSENBROCK', 'f':ROSENBROCK, 'bounds':[[-5, 10], [-5, 10]], 'opt':[[1, 1], 0]} | |
| self.DE_JONG_ = {'name':'DE_JONG', 'f':DE_JONG, 'bounds':[[-65.536, 65.536], [-65.536, 65.536]]} | |
| self.MICHALEWICZ_ = {'name':'MICHALEWICZ', 'f':MICHALEWICZ, 'bounds':[[0, np.pi], [0, np.pi]], 'opt':[[2.2, 1.57], -1.8013]} | |
| self.BEALE_ = {'name':'BEALE', 'f':BEALE, 'bounds':[[-4.5, 4.5], [-4.5, 4.5]], 'opt':[[3, 0.5], 0]} | |
| self.BRANIN_ = {'name':'BRANIN', 'f':BRANIN, 'bounds':[[-5, 10], [0, 15]], 'opt':[[-np.pi, 12.275], 0.397887]} | |
| self.GOLDSTEIN_PRICE_ = {'name':'GOLDSTEIN_PRICE', 'f':GOLDSTEIN_PRICE, 'bounds':[[-2, 2], [-2, 2]], 'opt':[[0, -1], 3]} | |
| self.PERM_D_BETA_ = {'name':'PERM_D_BETA', 'f':PERM_D_BETA, 'bounds':[[-2, 2], [-2, 2]], 'opt':[[1, 2], 0]} | |
| self.dictionary = {'Ackley': self.Ackley_, | |
| 'sixth_Bukin':self.sixth_Bukin_, | |
| 'Cross_in_Tray':self.Cross_in_Tray_, | |
| 'Drop_Wave':self.Drop_Wave_, | |
| 'Eggholder':self.Eggholder_, | |
| 'Griewank':self.Griewank_, | |
| 'Holder_Table':self.Holder_Table_, | |
| 'Levy':self.Levy_, | |
| 'Rastrigin':self.Rastrigin_, | |
| 'second_Schaffe':self.second_Schaffe_, | |
| 'fourth_Schaffer':self.fourth_Schaffer_, | |
| 'Schwefel':self.Schwefel_, | |
| 'Shubert':self.Shubert_, | |
| 'Styblinski_Tang':self.Styblinski_Tang_, | |
| 'Easom':self.Easom_, | |
| 'Bohachevsky':self.Bohachevsky_, | |
| 'Perm_d_beta':self.Perm_d_beta_, | |
| 'Rotated_Hyper_Ellipsoid':self.Rotated_Hyper_Ellipsoid_, | |
| 'Sum_of_Different_Powers': self.Sum_of_Different_Powers_, | |
| 'SUM_SQUARES':self.SUM_SQUARES_, | |
| 'TRID':self.TRID_, | |
| 'BOOTH': self.BOOTH_, | |
| 'Matyas':self.Matyas_, | |
| 'MCCORMICK': self.MCCORMICK_, | |
| 'Power_Sum':self.Power_Sum_, | |
| 'Zakharov':self.Zakharov_, | |
| 'THREE_HUMP_CAMEL' :self.THREE_HUMP_CAMEL_, | |
| 'SIX_HUMP_CAMEL': self.SIX_HUMP_CAMEL_, | |
| 'DIXON_PRICE': self.DIXON_PRICE_, | |
| 'ROSENBROCK_': self.ROSENBROCK_, | |
| 'DE_JONG':self.DE_JONG_, | |
| 'MICHALEWICZ': self.MICHALEWICZ_, | |
| 'BEALE':self.BEALE_, | |
| 'BRANIN': self.BRANIN_, | |
| 'GOLDSTEIN_PRICE':self.GOLDSTEIN_PRICE_, | |
| 'PERM_D_BETA':self.PERM_D_BETA_} | |
| self.whole_list = list(self.dictionary.keys()) | |
| def plotly_graph(problem, df=None): | |
| function=problem['f'] | |
| x_lb=problem['bounds'][0][0] | |
| x_ub=problem['bounds'][0][1] | |
| y_lb=problem['bounds'][1][0] | |
| y_ub=problem['bounds'][1][1] | |
| x = np.linspace(x_lb, x_ub, 100) | |
| y = np.linspace(y_lb, y_ub, 100) | |
| z = np.empty((100, 100)) | |
| for ind_y, j in enumerate(y): | |
| for ind_x, i in enumerate(x): | |
| z[ind_y][ind_x] = function(np.array([i, j])) | |
| steps_ = int(np.max(df['iter'])) | |
| fig1 = go.Figure(data=[go.Surface(x=x, y=y, z=z)]) | |
| for step in range(steps_): | |
| points = df[df['iter']==step] | |
| points_x = list(points['x1']) | |
| points_y = list(points['x2']) | |
| points_z = list(points['f']) | |
| fig1.add_scatter3d(x=np.array(points_x), y=np.array(points_y), z=np.array(points_z), mode='markers', visible=False, marker=dict(size=5, color="white", line=dict(width=1, color="black"))) | |
| fig1.update_layout(title=f"f = {step}") | |
| # Create figure | |
| fig = go.Figure(data=[go.Scatter3d(x=[], y=[], z=[], | |
| mode="markers", | |
| marker=dict(size=5, color="white", line=dict(width=1, color="black")) | |
| ), fig1.data[0]] | |
| ) | |
| # Frames | |
| frames = [go.Frame(data=[go.Scatter3d(x=k['x'], | |
| y=k['y'], | |
| z=k['z'] | |
| ), fig1.data[0] | |
| ], | |
| traces= [0], | |
| name=f'frame{ind}' | |
| ) for ind, k in enumerate(fig1.data[1:]) | |
| ] | |
| fig.update(frames=frames) | |
| def frame_args(duration): | |
| return { | |
| "frame": {"duration": duration}, | |
| "mode": "immediate", | |
| "fromcurrent": True, | |
| "transition": {"duration": duration, "easing": "linear"}, | |
| } | |
| sliders = [ | |
| {"pad": {"b": 10, "t": 60}, | |
| "len": 0.9, | |
| "x": 0.1, | |
| "y": 0, | |
| "steps": [ | |
| {"args": [[f.name], frame_args(0)], | |
| "label": str(k), | |
| "method": "animate", | |
| } for k, f in enumerate(fig.frames) | |
| ] | |
| } | |
| ] | |
| fig.update_layout( | |
| updatemenus = [{"buttons":[ | |
| { | |
| "args": [None, frame_args(150)], | |
| "label": "Play", | |
| "method": "animate", | |
| }, | |
| { | |
| "args": [[None], frame_args(150)], | |
| "label": "Pause", | |
| "method": "animate", | |
| }], | |
| "direction": "left", | |
| "pad": {"r": 10, "t": 70}, | |
| "type": "buttons", | |
| "x": 0.1, | |
| "y": 0, | |
| } | |
| ], | |
| sliders=sliders | |
| ) | |
| fig.update_layout(sliders=sliders) | |
| fig.write_html('animation.html') | |
| return fig | |
| #problem=test_functions().PERM_D_BETA_ | |
| #df, best_var, best_sol = custom_pso(problem, 20, 2, maxiter=100, solver="Coulomb", k=0.00000000000000001, G=0.00000001, t=1.0, max_q=0.01, auto_reduce_search_space=True, dinamic=True) | |
| #plotly_graph(problem, df) | |
| #print(best_sol) | |
| ``` | |
| ''') | |
| st.markdown("### 4. Experimentación") | |
| if "results" not in st.session_state: | |
| st.session_state.results = {'df':[], 'fig':[], 'best_solution':None, 'best_f':None} | |
| c1, c2 = st.columns(2) | |
| options = test_functions().whole_list | |
| selected = c1.selectbox("choose problem", options, index=1) | |
| problem = test_functions().dictionary[selected] | |
| solver_list = ['Random', 'Newton', 'Coulomb'] | |
| solver_selected = c2.selectbox("choose solver", options=solver_list, index=0) | |
| st.markdown("Hyperparameters") | |
| # hyperparameters | |
| auto_constaint_choosen=False | |
| G=0.0000000001 | |
| k=0.0000000001 | |
| t=1.0 | |
| options_as_mt=['Linear', 'Quadratic', 'Exponential'] | |
| assing_method='Linear' | |
| if solver_selected == "Random": | |
| auto_constaint = st.selectbox("auto_constraint_domain", options=['False', 'True'], index=1) | |
| dic_auto_constaint = {'False':False, 'True':True} | |
| auto_constaint_choosen=dic_auto_constaint[auto_constaint] | |
| elif solver_selected == "Newton": | |
| c1, c2, c3 = st.columns(3) | |
| G=c1.number_input("G", min_value=0.0, max_value=8.0, value=0.0000000001) | |
| assing_method = c2.selectbox("assing mehod", options=options_as_mt, index=0) | |
| t=c3.number_input("t", min_value=0.01, max_value=10.0, value=1.0) | |
| elif solver_selected == "Coulomb": | |
| c1, c2, c3 = st.columns(3) | |
| k=c1.number_input("k", min_value=0.0, max_value=8.0, value=0.0000000001) | |
| assing_method = c2.selectbox("assing mehod", options=options_as_mt, index=0) | |
| t=c3.number_input("t", min_value=0.01, max_value=10.0, value=1.0) | |
| solve_bt = st.button("Solve") | |
| if solve_bt: | |
| df, best_solution, best_f = custom_pso(problem, nPart=20, nVar=2, maxiter=100, solver=solver_selected, auto_reduce_search_space=auto_constaint_choosen, G=G, t=t, keep_best=True, k=k, dinamic=True, weight_method=assing_method) | |
| fig = plotly_graph(problem, df) | |
| st.session_state.results['df'], st.session_state.results['best_solution'], st.session_state.results['best_f'], st.session_state.results['fig'] = df, best_solution, best_f, fig | |
| if len(st.session_state.results['df'])>0: | |
| results = st.session_state.results | |
| df, best_solution, best_f = results['df'], results['best_solution'], results['best_f'] | |
| with st.expander('Performance'): | |
| c1, c2 = st.columns([4, 1]) | |
| c1.dataframe(df) | |
| c2.markdown('Best Solution:') | |
| df = pd.DataFrame(best_solution) | |
| df.columns = ['value'] | |
| c2.dataframe(df) | |
| c2.markdown(f'Best Value: `{best_f}`') | |
| fig = results['fig'] | |
| st.plotly_chart(fig) | |