UNIVERSIDAD DE OVIEDO Facultad de Ciencias Licenciatura en Matemáticas Algoritmos Evolutivos para la Resolución de Sistemas de “Word Equations” Fátima Drubi Vega Trabajo Académico Oviedo, julio de 2003 Índice general 1. Introducción 4 2. Los Algoritmos Genéticos 6 2.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.2. Conceptos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3. Caracterı́sticas de los Algoritmos Genéticos . . . . . . . . . . . . . . . . . . 9 2.4. El Algoritmo Genético Simple . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.4.1. Codificación de los Individuos . . . . . . . . . . . . . . . . . . . . . . 11 2.4.2. Generación de la Población Inicial . . . . . . . . . . . . . . . . . . . 14 2.4.3. Evaluación de los Individuos . . . . . . . . . . . . . . . . . . . . . . 14 2.4.4. Selección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4.5. Cruce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.4.6. Mutación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.5. Seudocódigo de un Algoritmo Genético Simple . . . . . . . . . . . . . . . . 22 2.6. El Teorema de los Esquemas . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.7. Otros Algoritmos Genéticos: los Algoritmos Evolutivos . . . . . . . . . . . . 34 3. El Problema de Satisfactibilidad 36 3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.2. Conceptos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.3. Los Algoritmos Evolutivos para Resolver SAT . . . . . . . . . . . . . . . . . 39 4. Sistemas de “Word Equations”: el Algoritmo de Makanin 48 4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 4.2. Conceptos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 1 4.3. El Algoritmo de Makanin . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.3.1. Representación Gráfica . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.3.2. Las Ecuaciones Generalizadas . . . . . . . . . . . . . . . . . . . . . . 55 4.3.3. EL Algoritmo de Transformación . . . . . . . . . . . . . . . . . . . . 60 4.4. Casos Particulares de Sistemas de “Word Equations” . . . . . . . . . . . . . 66 5. Un Algoritmo Genético Simple para Resolver l SW ES 71 5.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.2. Codificación de los Individuos . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.3. Generación de la Población Inicial . . . . . . . . . . . . . . . . . . . . . . . 74 5.4. Evaluación de los Individuos . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 5.5. Los Operadores Genéticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 6. El Algoritmo Genero Problema 88 7. Primeros Resultados Experimentales 91 7.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 7.2. Tamaño de Población . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 7.3. Probabilidad de mutación . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 8. Algoritmos Evolutivos para Resolver l SW ES 107 8.1. A.E. Sólo Mutación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 8.2. A.E. Búsqueda Local . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 8.3. Resultados experimentales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 9. Experimento final 125 A. El Algoritmo Genético Simple 128 B. El Algoritmo Genero Problema 145 C. Los problemas propuestos 157 C.1. El problema 10-15-3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 C.2. El problema 10-15-5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 C.3. El problema 10-3-3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 C.4. El problema 10-5-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 2 C.5. El problema 10-5-2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 C.6. El problema 10-8-3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 C.7. El problema 10-8-5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 C.8. El problema 12-6-4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 C.9. El problema 15-12-4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 C.10.El problema 15-25-5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 C.11.El problema 15-7-5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 C.12.El problema 25-23-4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 C.13.El problema 25-8-3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 C.14.El problema 25-8-5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 C.15.El problema 5-15-3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 C.16.El problema 5-3-2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 D. A.E. Sólo Mutación 168 E. A.E. Búsqueda Local 179 Bibliografı́a 204 3 Capı́tulo 1 Introducción En este trabajo hemos intentado reunir los resultados obtenidos a lo largo del curso que comenzamos con el objetivo de diseñar un algoritmo que proporcione una solución a un sistema de ecuaciones conocido como sistema de “word equations”. El capı́tulo 2 lo dedicamos al estudio del tipo de algoritmos que pretendemos usar para resolver estos sitemas, los llamados algoritmos evolutivos. Podrı́amos decir que en este capı́tulo se encuentran las herramientas básicas necesarias para su diseño. Además, como aplicación de los mismos a la resolución de diferentes problemas, estudiamos en el capı́tulo 3 los llamados problemas SAT y explicamos el funcionamiento de los algoritmos evolutivos más conocidos para su resolución. Algunos de estos algoritmos nos servirán de referencia a la hora de diseñar el nuestro pues, como veremos, los problemas SAT se reducen a sistemas de “word equations”. En el capı́tulo 4 introducimos el problema que pretendemos resolver: los sistemas de “word equations”, e intentamos resumir la teorı́a que ya ha sido desarrollada sobre estos sistemas, destacando las dificultades que aparecen en su resolución, pues aunque existe un algoritmo que los resuelve, el algoritmo de Makanin, éste no se puede usar en la práctica por ser un algoritmo con una complejidad triplemente exponencial. En los siguientes capı́tulos explicamos las distintas propuestas que emplearemos para el diseño del algoritmo evolutivo. Nuestro trabajo propiamente dicho comienza en el capı́tulo 5 diseñando un algoritmo evolutivo denominado algoritmo genético simple, cuyos primeros resultados experimentales aparecen en el capitulo 7. En el capı́tulo 6, hacemos un pequeño paréntesis para diseñar un algoritmo que construya sistemas de “word equations”, pues no existen 4 documentos donde podamos encontrar estos problemas. En el capı́tulo 8, obtenemos nuevos algoritmos evolutivos añadiendo algunas modificaciones al algoritmo genético simple. Los resultados experimentales nos permitirán comprobar las mejoras obtenidas al añadir búsqueda local al algoritmo genético simple. Finalizamos exponiendo los resultados obtenidos del mejor de los algoritmos evolutivos que hemos diseñado a lo largo de este curso. Todos los algoritmos diseñados se encuentran en los distintos apéndices, escritos en el lenguaje C++. También aparecen algunos de los sistemas de “word equations” que hemos utlizado en los diferentes experimentos. 5 Capı́tulo 2 Los Algoritmos Genéticos 2.1. Introducción Un algoritmo genético es un proceso de búsqueda basado en los mecanismos de la evolución biológica: selección natural, reproducción y mutación. Fueron desarollados por John Holland [12] , que desde pequeño se preguntaba cómo la naturaleza logra crear seres cada vez más perfectos. Aunque esto no es totalmente cierto, resulta curioso observar que todo tiene lugar mediante interacciones locales entre individuos. De la lectura del libro titulado “La teorı́a genética de la selección natural” aprendió que la evolución era una forma de adaptación más potente que el simple aprendizaje. Los objetivos de su búsqueda han sido dos: Abstraer y explicar rigurosamente los procesos de adaptación de los sistemas naturales. Diseñar sistemas artificiales que conserven los mecanismos de los sistemas naturales. Su primer monográfico sobre los algoritmos genéticos se publicó en 1975, “Adaptation in Natural and Artificial Systems”. Algunos artı́culos y tesis posteriores establecen la validez de la técnica en la optimización de funciones y aplicaciones de control. Los algoritmos genéticos son computacionalmente simples, aunque potentes en su búsqueda y mejora, y han sido aceptados como una aproximación válida a problemas que requieren búsqueda eficiente y eficaz. Además, no están limitados por supuestas restricciones sobre el espacio de búsqueda, como la continuidad o la existencia de derivadas. 6 2.2. Conceptos Básicos Definición 1 Se llama alfabeto a un conjunto finito de sı́mbolos dados. Se denotará por A. Ejemplo 1 Los siguientes conjuntos pueden ser alfabetos 1. { 1, 3, 5, 7, 9} 2. { a, b, c,. . . , x, y, z} 3. {α, β, γ} 4. {si, no, inicio, fin} 5. { 0, 1} Definición 2 Una cadena o palabra es una sucesión ordenada y finita de sı́mbolos de un alfabeto. Se denotará por ω. Definición 3 El número de sı́mbolos que constituyen una cadena ω se llama longitud de ω. Se denotará por |ω|. Ejemplo 2 ω = 10011 es una cadena de longitud |ω| = 5 del alfabeto binario A = { 0, 1}. Definición 4 Se dice cadena vacia a la cadena que no contiene sı́mbolos y por lo tanto tiene longitud cero. Se denotará por Λ. Definición 5 El exponente de periodicidad de una cadena ω es el máximo número p tal que ω = uv p z donde u, v y z son cadenas con v 6= Λ. 7 Definición 6 Dado un alfabeto A. Se define el conjunto de todas las cadenas que se pueden formar con sı́mbolos de A como A∗ = {Λ} ∪ A ∪ A2 ∪ . . . ∪ An ∪ . . . = [ Ak k≥0 siendo Ak el conjunto de cadenas de longitud k. Definición 7 Sea A un alfabeto. Dadas dos cadenas ω1 y ω2 sobre A se define la concatenación de ω1 y ω2 como la cadena que resulta de colocar primero ω1 y a continuación ω2 . Se denotará ω1 ω2 . Propiedades de la concatenación: Asociativa, ( ω1 ω2 ) ω3 = ω1 ( ω2 ω3 ) ∀ ω1 , ω2 , ω3 ∈ A. Existencia de elemento neutro, Λω=ω Λ ∀ ω ∈ A. |ω1 ω2 | = |ω1 | + |ω2 | En general, no es conmutativa. Ejemplo 3 Si ω1 = ab y ω2 = b entonces ω1 ω2 = abb 6= bab = ω2 ω1 Nota 1 El alfabeto A con la operación de concatenación tiene estructura de semigrupo con identidad. Nota 2 Será frecuente usar el término “bit” para referirnos a los sı́mbolos de un alfabeto, en particular, cuando trabajemos con el alfabeto binario A = { 0, 1}. 8 2.3. Caracterı́sticas de los Algoritmos Genéticos Algunas de las principales caracterı́sticas de los algoritmos genéticos, que los diferencia de otros procesos de optimización, son las siguientes: Uso directo de un código. Trabajan con una codificación del conjunto de variables del problema, no con las propias variables. En general, se codifican como cadenas de algún alfabeto. Búsqueda de una población. Buscan un conjunto de puntos, no un único punto. Mientras que algunos métodos de optimización se mueven en el espacio de decisión desde un único punto al siguiente usando reglas de transición, los algoritmos genéticos trabajan simultaneamente con un conjunto de puntos, reduciéndose al máximo la probabilidad de encontrar un falso óptimo. Ceguedad sobre información auxiliar. Usan la información de la función objetivo del problema de optimización, no sus derivadas u otros conocimientos auxiliares. Aleatoriedad controlada. Utilizan reglas de transmisión probabilı́sticas, nunca reglas determinı́sticas. Es importante no confundir los métodos de búsqueda estrictamente aleatorios con las técnicas aleatorias. El algoritmo genético es un ejemplo de un proceso de búsqueda que utiliza selección aleatoria como una herramienta para guiar una búsqueda muy explosiva mediante una codificación sencilla de un espacio paramétrico. No estamos hablando de un simple proceso aleatorio, es decir, no se trata de tomar una decisión a cara o cruz, sino que definimos un método de búsqueda que aprovecha la información histórica para especular sobre nuevos puntos. Estas cuatro caracterı́sticas contribuyen a dar fuerza a los algoritmos genéticos y resultan de gran utilidad, por encima incluso de otras técnicas usadas con más frecuencia. 9 Además, la habilidad del algoritmo genético para explotar la información acumulada sobre un espacio de búsqueda permite su aplicación a espacios de búsqueda grandes, complejos y poco entendidos. Por lo tanto, los algoritmos genéticos tratan de alcanzar un equilibrio entre dos objetivos aparentemente en conflicto: explotar las buenas cualidades de las candidatas a solución y explorar el espacio de búsqueda. La simplicidad de las operaciones y la gran eficacia del proceso son dos de los principales atractivos de los algoritmos genéticos. Veremos que la explicación de por qué este proceso funciona es mucho más sutil. 2.4. El Algoritmo Genético Simple Antes de examinar el funcionamiento interno de un algoritmo genético simple y estudiar su potencia, tenemos que tener claros cuáles son nuestros objetivos cuando decimos que queremos optimizar una función o un proceso. Es evidente que el objetivo más importante de la optimización es la mejora. Definición 8 Se llama individuo a cualquier cadena candidata a ser solución del problema que se pretende resolver mediante un algoritmo genético. Un conjunto finito de individuos se dice población. Definición 9 Se llama función fitness, de calidad, o de aptitud... a aquella función que determina los individuos mejor adaptados o, de alguna manera, más cercanos a la solución del problema. Para seleccionar una función fitness adecuada hay que hacer un balance entre aquellas que reflejen diferencias muy grandes (provocando una convergencia prematura de los individuos de la población hacia un óptimo local) y las que proporcionen diferencias pequeñas (dando lugar a un estacionamiento). En ocasiones se añade un factor de penalización para controlar los individuos que no responden a las caracterı́sticas de las soluciones del problema. El algoritmo genético simple está constituido principalmente por tres etapas bien diferenciadas: selección, cruce y mutación. Sin embargo, en la práctica es necesario especificar para su diseño los siguientes aspectos: 10 la codificación de las variables del problema que constituirán los individuos el modo de generar la población inicial la función fitness que evaluará la población el criterio de selección el tipo de cruce y la forma en que se aplicará el proceso de mutación. 2.4.1. Codificación de los Individuos Los operadores genéticos (selección, cruce y mutación) dependen del tipo de representación utilizado. Originalmente, las soluciones se representaban por cadenas binarias, es decir, listas de ceros y unos. Este tipo de representación permite definir fácilmente el proceso de cruce. Sin embargo, en algunos problemas resulta poco natural y eficiente. Por este motivo, es importante estudiar el problema que se pretende resolver para determinar la representación más adecuada de los individuos y, en consecuencia, de la solución que se busca. Ejemplo 4 Consideramos el problema del viajante dado por 5 ciudades y 20 aristas. Hay que calcular un camino cerrado que pase por todas las ciudades, sólo una vez por cada ciudad, y tal que la distancia recorrida sea mı́nima. Sean C1 , C2 , C3 , C4 y C5 las ciudades que hay que recorrer. Suponemos que el viajante parte siempre de la ciudad C1 . Tenemos que codificar cinco variables, una por cada una de las ciudades del problema. Proponemos codificarlas como cadenas de longitud 4 del alfabeto A = { 0, 1} pues a cada ciudad llegan y salen cuatro aristas. De esta forma, representamos para cada ciudad las aristas que salen de esta y le asignamos el valor uno a aquella que forma parte del camino elegido. Ası́, la cadena 0010 |{z } 0100 |{z} 0010 |{z } 1000 |{z } |{z } 1000 C1 C2 C3 C4 C5 representa una candidata a solución sobre las aristas ordenadas. 11 C2 0 C4 0 0 1 C3 C1 C3 1 C4 0 0 C5 0 C1 0 C5 1 1 1 C5 ... 0 0 C2 Sin embargo, no resulta una representación natural y, además, no todos los individuos que tienen cinco unos representan una solución del problema. La cadena 11101100000000000000 ni siquiera representa un camino en el grafo de bits correspondiente, pues cada subcadena representando a una ciudad debe contener un uno y tres ceros. Ası́ mismo, la cadena 01001000001000101000 aunque si representa un camino no permite recorrer todas las ciudades y no puede ser aceptada como solución candidata del problema. Con esta representación de las variables nos vemos obligados a controlar las cadenas generadas para garantizar que se visitan todas las ciudades una sóla vez cada una. Después, comprobamos cuál es la que hace mı́nima la distancia recorrida. C2 M w 1 C3 C1 ) ] C4 C5 Observamos que las posibles soluciones son de la forma C2 7 C2 w C2 k M C3 C1 i 1 C3 / C1 C5 C3 6 C1 C4 C5 12 C4 ] q C4 ? C5 Por lo tanto, resulta más natural representar la ruta de las ciudades por una permutación, digamos (5, 4, 2, 3) sobre el alfabeto A0 = {2, 3, 4, 5} que permite definir diferentes poblaciones estables, es decir, todos los individuos ası́ generados representan posibles soluciones. La permutación (5, 4, 2, 3) se interprata de la siguiente forma: partiendo de la ciudad C1 el viajante se desplaza hasta la ciudad C5 , de ahı́ se mueve a C4 , después sigue hacia C3 pasando primero por la ciudad C2 y finalmente vuelve a C1 . 2 ] ~3 1 9 ~ 4 5 3 Con esta representación sólo tenemos que preocuparnos de encontrar la permutación que haga mı́nima la distancia necesaria para recorrer todas las ciudades. Además, el problema del viajante en su enunciado general no exige que el camino comience en una ciudad concreta sino que se enuncia en los siguientes términos: “Construir, si existe, un camino de coste mı́nimo que pase por cada una de las cinco ciudades una sóla vez” Con este enunciado y suponiendo, como hasta ahora, que todas las ciudades estan comunicadas entre sı́ en ambos sentidos, la mejor representación de los individuos está formada por permutaciones de cinco elementos, pues la representación binaria necesitarı́a además indicar cuál es la ciudad inicial del camino para cada individuo. Aunque existen múltiples tipos de representación, la mayorı́a de los resultados en la teorı́a de los algoritmos genéticos, como el teorema de los esquemas que estudiaremos posteriormente, han sido desarrollados sobre el alfabeto binario. Nota 3 Una codificación correcta es una de las claves más importantes para tener exito en la resolución de los problemas. 13 2.4.2. Generación de la Población Inicial Una vez definida la codificación de los individuos, el primer paso en un algoritmo genético es la creación de una población inicial de soluciones del problema, debidamente codificada. Normalmente, se fija el tamaño de la población y se generan de forma aleatoria los individuos para conseguir una población inicial amplia y representativa del espacio de búsqueda. En los últimos tiempos, se han ido incorporando métodos heurı́sticos a este proceso con el fin de generar individuos en la población inicial con un buen fitness. En este caso, hay que garantizar la diversidad estructural de estos individuos para tener una representación de la mayor parte de la población posible o, al menos, evitar la convergencia puntual. También existen algoritmos donde el tamaño de la población es variable. 2.4.3. Evaluación de los Individuos Cada vez que se genera una población de individuos es necesario evaluarlos para obtener una medida de calidad, es decir, el fitness asociado a cada individuo. Este valor es utilizado en el proceso de selección para la obtención de las sucesivas generaciones. Como ya se mencionó, la elección del fitness que guiará el proceso de búsqueda será una de las claves para garantizar el buen funcionamiento del algoritmo genético. Ejemplo 5 En el problema del viajante anterior, en su versión general, generamos de forma aleatoria una población de tamaño tp = 3. Como se trata de un problema de minimización podemos tomar como función fitness f la función que nos proporciona la distacia recorrida. De este modo, un individuo será mejor que otro si la función fitness asociada tiene un valor más pequeño. Definimos dij la distancia entre las ciudades Ci y Cj para i, j = 1, 2, 3, 4, 5 con i 6= j. Suponemos que d12 = 12 Km d13 = 40 Km d14 = 31 Km d15 = 65 Km d23 = 26 Km d24 = 13 Km d25 = 78 Km d34 = 43 Km d35 = 37 Km d45 = 52 Km Se tiene la siguiente tabla 14 Individuo fitness I1 = (1, 4, 2, 3, 5) 172 I2 = (2, 3, 5, 4, 1) 158 I3 = (3, 1, 4, 2, 5) 199 Luego, la ruta que proporciona el individuo I2 es la mejor de las tres rutas de la población. 2.4.4. Selección La selección es un operador genético mediante el cual cada individuo es copiado de acuerdo a los valores que le asocia la función fitness. Se trata de una versión artificial de la selección natural, donde el ser mejor adaptado tiene mayor probabilidad de sobrevivir. Pensemos en la función fitness como una medida de ganancia, utilidad o bondad que queremos maximizar. Los individuos que tienen asociado un valor más alto de la función fitness tienen una mayor probabilidad de contribuir en uno o más descendientes en la siguiente generación. La implementación en forma algorı́tmica del proceso de selección se realiza mediante una ruleta, donde a cada individuo de la población le corresponde una sección circular directamente proporcional a la probabilidad que tiene de contribuir en la siguiente generación. De esta forma, cada vez que se tiene que seleccionar un individuo se realiza una tirada de bola en la ruleta tomando aquel asociado a la sección circular donde cayó la bola. La probabilidad de contribuir en la siguiente generación viene dada por P(i) = f itness(i) tp X f itness(j) j=1 para cada individuo i = 1, . . . , tp. Ejemplo 6 Dada la función f : N −→ N x −→ x2 ¿Cuál es el valor máximo que alcanza f en el intervalo entero [0, 31]? Codificamos la única variable del problema, x, como una cadena de longitud 5 en el alfabeto A = { 0, 1}. De esta forma, podemos utilizar la numeración binaria para relacionar el valor de x con su correspondiente codificación. 15 0 −→ 00000 4 −→ 00100 17 −→ 10001 31 −→ 11111 Como se trata de un problema de optimización (maximizar f ) podemos tomar como función fitness la propia función f . Suponemos un tamaño de población tp = 4. Consideramos la población inicial, generada de forma aleatoria, I1 = 01010 I2 = 01001 I3 = 11100 I4 = 00010 Al evaluar estos indivios se tiene la siguiente tabla Individuo Pob. Inicial Valor de x f (x) = x2 I1 01010 10 100 0.19 I2 01001 18 324 0.6 I3 11100 7 49 0.09 I4 00010 8 64 0.12 P (i) = fi / P fj Se deduce que el individuo I2 es el que más probabilidad tiene de contribuir en la generación siguiente, por eso, la sección circular que le corresponde en la ruleta es la más grande. qqqqq qqqqqqqqqqqqqq qqqqqqqqqqqqqq qqqqqqq qqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqq q q q q qqqqqq qqqq qqqq qqqqq qqqqq qqqq qqq qqqqq qqqq qqq qq qq qqqqq 19 % 12 % qqqqq qqqqq qqq qq qqq qqq q q qq qq 9 % qqqqq qqqq qqqq qqqq q q qqq qq qq qqqq qqq qqqq q qq qq qqq qq qqq qqqq qq qqqq q 60 % q qqq qqqqq qqqq qq qqqq qqqqq qqqqq qqqqqqq qqqqq qqqqqqqqq q q q q q q q qqqqqqq qqqqqqqqqqqqqqqqqqqq qqqqqqqq qqqqqqqqqq q qqqqqqqqqqqqqqqqqqqqqqqqq Nota: La selección es con reemplazamiento, es decir, un mismo individuo puede ser copiado varias veces. Por lo tanto, los individuos mejor adaptados tienen mayor probabilidad de sobrevivir. Existen otras técnicas para la selección de los individuos, como las denominadas: Torneo: Se seleccionan dos individuos aleatoriamente y se elige el más apto con una probabilidad p fijada. (el menos apto se elige con probabilidad (1 − p)) 16 Ranqueo: Se ordena la población según el fitness de cada individuo y se le asigna la probabilidad de selección de acuerdo a la posición que ocupa. 2.4.5. Cruce Un sencillo proceso de cruce, denominado cruce en un punto, se realiza en dos etapas. En la primera, los individuos de la población elegidos de acuerdo al criterio de selección establecido son emparejados al azar. Recuérdese que una pareja puede estar formada por el mismo individuo repetido. En la segunda etapa, cada par de individuos experimenta un cruce como sigue: 1. Se selecciona al azar una posición entera entre uno y la longitud de la cadena, l, menos uno. Sea k ∈ {1, . . . , l − 1} la posición seleccionada al azar. 2. Se obtienen dos nuevas cadenas al intercambiar las subcadenas situadas entre las posiciones k + 1 y l, ambas inclusive. Ejemplo 7 Consideramos las cadenas w1 y w2 de la población inicial en el ejemplo anterior, w1 = 01010 w2 = 01001 Como la longitud de estas cadenas es l = 5, tenemos que elegir la posición k como valor entero entre 1 y 4. Suponemos k = 4 (como indica el sı́mbolo separador | ) w1 = 0101|0 w2 = 0100|1 El cruce resultante produce dos nuevas cadenas, w10 = 01011 w20 = 01000 Ası́ se genera una nueva población de individuos que hereda parte de la información de la población anterior. 17 Nota 4 Se pueden seleccionar tantos individuos de la población actual como sean necesarios para generar una nueva población que reemplace completamente a la actual. Sin embargo, en muchas ocasiones se copian los N mejores individuos de la población actual a la siguiente de modo que sólo se seleccionan los individuos necesarios para completar la nueva población. Este tipo de selección se denomina elitista. Normalmente, se suele copiar el mejor o los dos mejores individuos de cada población a la siguiente. De esta forma se mantiene siempre en la población al mejor individuo alcanzado hasta el momento. Existen muchos otros procesos de cruce que se van adaptando a las necesidades concretas de cada problema. A continuación definimos algunos de los más conocidos. Definición 10 (Cruce en n puntos) Se dice cruce en n puntos cuando dadas dos cadenas de longitud l se eligen n posiciones enteras {ki }i=1,...,n tal que ki ∈ {1, . . . , l − 1} y se intercambian las subcadenas situadas entre estas posiciones. Se trata de la generalización del cruce en un punto. Ejemplo 8 Cosideramos dos individuos de longuitud l = 15 dados por 010101000001000 100100101011101 Vamos a aplicar un cruce de 4 puntos. Para ello seleccionamos al azar cuatro posiciones enteras entre 1 y 14. Supongamos que k1 = 3, k2 = 7, k3 = 9 y k4 = 13, entonces 010|1010|00|0010|00 : 100|1010|01|0010|01 100|1001|01|0111|01 z 010|1001|00|0111|00 Hemos obtenido dos nuevos individuos, 100101001001001 010100100011100 18 Definición 11 (Cruce uniforme) Se dice cruce uniforme cuando se cruzan dos cadenas dadas de longitud l mediante un proceso que compara los sı́mbolos que ocupan las mismas posiciones en ambas cadenas, es decir, para cada posición i = 0, . . . , l se compara el sı́mbolo que ocupa la i ésima posición de la primera cadena con el que ocupa la i ésima posición de la segunda cadena. El más utilizado cruce uniforme, genera una nueva cadena cruzando dos cadenas como sigue: para cada i = 0, . . . , l, si los simbolos que ocupan la i ésima posición coinciden, entonces se copia dicho sı́mbolo a la posición i ésima de la nueva cadena, en otro caso, se elige uno de los dos sı́mbolos comparados (con probabilidad p se elige el de la primera cadena y con probabilidad (1-p) el de la segunda) y se copia a la i ésima posición de la nueva cadena. Ejemplo 9 Dadas dos cadenas sobre el alfabeto A = { 0, 1} ω1 = 01010111 ω2 = 00101111 Obtenemos una nueva cadena aplicando un cruce uniforme que elige el sı́mbolo de ω1 con probabilida 0,3 y el sı́mbolo de ω2 con probabilidad 0,7 cuando los sı́mbolos que se comparan no coinciden. Comparamos los sı́mbolos que ocupan la primera posición, como coinciden se copia ese sı́mbolo a la nueva cadena. ω1 = 01010111 - ω2 = 00101111 0 ··· Al comparar los sı́mbolos que ocupan la segunda posición, y teniendo en cuenta la probabilidad de cruce, ha resultado que el sı́mbolo de ω1 se copia a la nueva cadena. ω1 = 01010111 - ω2 = 00101111 0 1 ··· Repitiendo el proceso hasta la última posición de ambas cadenas podrı́a obtenerse ω1 = 01010111 - ω2 = 00101111 19 0 1 1 0 1 1 1 1 Existen otros criterios para definir el operador de cruce uniforme, como por ejemplo: Cuando dos sı́mbolos no coincidan se intercambian entre sı́ con una determinada probabilidad, dando lugar a dos nuevas cadenas. Fijada una de las dos cadenas, se recorre sı́mbolo a sı́mbolo. Cada vez que se encuentre un uno se compara con el sı́mbolo que ocupa la misma posición en la otra cadena. Si no coinciden, entonces se intercambian estos dos sı́mbolos entre sı́, dando lugar a dos nuevas cadenas. Además, se puede imponer una probabilidad de cambio. Ejemplo 10 Dadas dos cadenas sobre el alfabeto A = { 0, 1} ω1 = 11001001 ω2 = 10011101 Para aplicar el cruce uniforme consideramos el criterio que intercambia dos sı́mbolos distintos con probabilidad p = 0,5. Empezamos a recorrer los sı́mbolos de ambas cadenas y encontramos los dos primeros que no coinciden 11001001 10011101 Al aplicar la probabilidad de intercambio resulta que no se cambian. Seguimos recorriendo las dos cadenas. Llegamos al siguiente punto donde los sı́mbolos de ambas cadenas no coinciden 11001001 10011101 Ahora sı́ se intercambian. Se obtienen dos nuevas cadenas 11001001 : 11011001 10011101 z 10001101 Este proceso se repite hasta que hayamos recorrido todos los sı́mbolos. Los nuevos individuos que resultan de aplicar el cruce uniforme vienen dados por 11011101 10001001 20 Los procesos de selección y cruce son sorprendentemente sencillos, involucrando generación aleatoria de números, copias de cadenas y algún intercambio parcial de cadenas. No obstante, el énfasis combinado de selección, aleatoriedad controlada e intercambio de información del cruce dan a los algoritmos genéticos gran parte de su potencia. Puede parecer sorprendente que dos operaciones tan simples resulten útiles en algún caso. Incluso, parece raro que el azar juege un papel tan fundamental en un proceso de búsqueda directo. 2.4.6. Mutación El operador de mutación juega un papel secundario en el algoritmo genético simple. Según algunos estudios, la frecuencia de mutación que permite obtener buenos resultados en la mayor parte de los problemas es del orden de una mutación por mil bits. Se sabe que en la naturaleza la probabilidad de mutación es aún más pequeña. Por eso podemos concluir que la mutación es considerada, apropiadamente, un mecanismo secundario. Sin embargo, este proceso nos permite dar un salto en otra dirección en el espacio de busqueda, evitando la convergencia prematura a óptimos locales. Se trata de un mecanismo generador de diversidad. Uno de los procesos de mutación más sencillos y utilizados consiste en reemplazar el valor de un bit de la población con cierta probabilidad. Otra forma de introducir nuevos individuos en una población es la recombinación de éstos tomados al azar, sin tener en cuenta el fitness. 21 2.5. Seudocódigo de un Algoritmo Genético Simple Una vez estudiados los elementos necesarios para construir un algoritmo genético simple, vamos a describir el funcionamiento del mismo. Fijados el tamaño de la población tp y la función fitness f que guiará el proceso, se genera una población inicial P0 donde cada individuo se obtiene de forma aleatoria. En la siguiente etapa, se evalúan los individuos de la población P0 para conocer el fitness asociado a cada uno. Finalmente, se realiza un bucle de iteraciones que consiste en la generación de nuevas poblaciones a partir de las anteriores mediante los operadores genéticos. De esta forma, en la iteración k ésima se genera la población Pk a partir de los individuos de la población Pk−1 usando selección, cruce y mutación. Cuando finaliza la ejecución del algoritmo, se obtiene una población formada por buenos individuos, siendo el mejor de ellos la solución que se propone. Seudocódigo de un AGS P0 ← generar población inicial E0 ← evaluar (P0 ) para k = 1 hasta NIter hacer para j = 1 hasta tp hacer Ij (1) ← seleccionar de Pk−1 Ij (2) ← seleccionar de Pk−1 Ij ← cruzar (Ij (1), Ij (2)) Ij ← mutar (Ij ) Ej ← evaluar (Ij ) Pk ← guardar (Ij ) fin para fin para Podı́amos estar interesados en conservar los N mejores individuos de cada población en la siguiente, es decir, aplicar una selección elitista. En el siguiente seudocódigo, guardamos el mejor individuo generado hasta el momento en la nueva población. 22 Seudocódigo de un AGS elitista P0 ← generar población inicial E0 ← evaluar (P0 ) para k = 1 hasta NIter hacer I ← mejor individuo de Pk−1 Pk ← guardar (I) para j = 1 hasta tp − 1 hacer Ij (1) ← seleccionar de Pk−1 Ij (2) ← seleccionar de Pk−1 Ij ← cruzar (Ij (1), Ij (2)) Ij ← mutar (Ij ) Ej ← evaluar (Ij ) Pk ← guardar (Ij ) fin para fin para Para finalizar esta sección, estudiaremos el funcionamiento de un algoritmo genético simple elitista aplicado a un problema concreto. Ejemplo 11 Se pretende maximizar el valor de la función f : [ −2, 2 ] −→ R x −→ exp( x2 ) cos(5x) (x+3)2 +1 Codificación de los individuos Tenemos que codificar una única variable x que toma valores reales entre −2 y 2. Para poder codificarlo como cadena del alfabeto binario A = { 0, 1} definimos la siguiente traslación: f˜ : [ 0, 4 ] −→ [ −2, 2 ] −→ R x̃ −→ x̃ − 2 −→ ) cos(5(x̃−2)) exp( x̃−2 2 ((x̃−2)+3)2 +1= exp( x̃2 −1) cos(5x̃−10) (x̃+1)2 Suponemos que la solución viene dada con tres cifras decimales. Luego, 0, 000 ≤ x̃ ≤ 4, 000 23 +1 Sabemos que 2048 = 211 < 4000 < 212 = 4096 Por lo tanto, podemos codificar la variable x̃ como una cadena ω de longitud |ω| = 12 en el alfabeto A = { 0, 1}. Nótese que 111111111111 = 4, 095 > 4, 000 Ası́ que cuando generemos los individuos de la población tendremos que controlar que el valor asociado a esa cadena es menor o igual que cuatro. Población inicial Trabajaremos con poblaciones de tamaño tp = 4. La población inicial será generada de forma totalmente aleatoria. Fitness Como se trata de un problema de maximizar f podemos tomar como fitness la propia función f . Criterio de selección Aplicamos en cada iteración selección elitista proporcional al fitness y que mantega en la población a los dos mejores individuos alcanzados hasta ese momento. Cruce Cada par de individuos seleccionados será cruzado en un punto. Sólo el mejor individuo obtenido de los dos que resultan del proceso de cruce se guardará en la nueva población . Mutación La probabilidad de que un individuo sea mutado será de 0,7 y la probabilidad de que un bit cambie su valor será de 1/12 = 0,083. Empezamos a resolver el problema ejecutando el algoritmo genético simple ası́ diseñado. Obtenemos la pobalción inicial P0 dada por los individuos: 24 I1 = 011001010010 I2 = 110000100101 I3 = 101110100100 I4 = 010000100001 Se tiene la siguiente tabla asociada a la población inicial P0 Individuo P0 x̃ f˜(x̃) P P (Ii ) = f˜i / f˜j I10 011001010010 1.190 0.914 0.23 I20 110000100101 2.627 0.896 0.22 I30 101110100100 0.605 1.149 0.28 I40 010000100001 2.114 1.092 0.27 Los dos mejores individuos de P0 , I30 y I40 , pasan directamente a la siguiente población, P1 . Individuo P1 x̃ f˜(x̃) I11 101110100100 0.605 1.149 I21 010000100001 2.114 1.092 ··· ··· ··· ··· Tenemos que generar otros dos individuos mediante los operadores genéticos para completar la población P1 . Generación de I31 : I30 = 10|1110100100 : 01|1110100100 con f˜(0,606) = 1,149 I10 = 01|1001010010 z 10|1001010010 con f˜(1,189) = 0,915 Luego, de los procesos de selección y cruce se tiene que I31 = 011110100100. Además, este individuo es sometido al proceso de mutación. Sin embargo, ninguno de sus bits muta. Generación de I41 : I30 = 1011101|00100 : 1100001|00100 con f˜(0,579) = 1,134 I20 = 1100001|00101 z 1011101|00101 con f˜(2,653) = 0,897 25 Luego, de los procesos de selección y cruce se tiene que I41 = 110000100100. Este individuo también es seleccionado para la mutación. En esta ocasión mutan los sı́mbolos situados en la sexta y duodécima posición. Se obtiene I41 = 110001100101 La nueva población P1 queda como sigue Individuo P1 x̃ f˜(x̃) P P (Ii ) = f˜i / f˜j I11 101110100100 0.605 1.149 0.27 I21 010000100001 2.114 1.092 0.25 I31 011110100100 0.606 1.149 0.27 I41 110001100101 2.659 0.897 0.21 Ahora, generamos la población P2 . En primer lugar, copiamos los dos mejores individuos de P1 , I11 y I31 . Después, para completar la población P2 , generamos dos individuos aplicando los operadores genéticos a P1 . I31 = 0111|10100100 : 0100|10100100 con f˜(0,594) = 1,143 I21 = 0100|00100001 z 0111|00100001 con f˜(0,126) = 1,088 I11 = 10111010|0100 : 101110100100 con f˜(0,605) = 1,149 I11 = 10111010|0100 z 101110100100 con f˜(0,605) = 1,149 De los procesos de selección y cruce se tiene que I32 = 010010100100 I42 = 101110100100 Nótese que para obtener I42 hemos cruzado el mismo individuo, I11 . Además, al individuo I32 se le aplica el operador de mutación. En este caso, los bits situados en la quinta, sexta y última posición mutan. Se obtiene I32 = 010001100101 Sin embargo, el individuo I42 no es sometido al proceso de mutación. Por lo tanto, la nueva población P2 ya está completamente generada. 26 Individuo P2 x̃ f˜(x̃) P P (Ii ) = f˜i / f˜j I12 101110100100 0.605 1.149 0.26 I22 011110100100 0.606 1.149 0.26 I32 010001100101 2.658 0.897 0.22 I42 101110100100 0.605 1.149 0.26 Para generar la siguiente población, tenemos que elegir los dos mejores individuos de P2 . Como el mejor fitness se tiene para los individuos I12 , I22 y I42 , elegimos al azar dos. Estos serán los que pasen directamente a la siguiente población, P3 . Para completar la población aplicamos los operadores genéticos a la población P2 . Se obtiene la siguiente tabla para la población P3 . Individuo P3 x̃ f˜(x̃) I13 011110100100 0.606 1.149 0.25 I23 101110100100 0.605 1.149 0.25 I33 011110100100 0.606 1.149 0.25 I43 010010100100 0.594 1.143 0.25 P (Ii ) = f˜i / P˜ fj Repetimos los pasos para generar la población P4 . Individuo P4 x̃ f˜(x̃) P P (Ii ) = f˜i / f˜j I14 101110100100 0.605 1.149 0.25 I24 011110100100 0.606 1.149 0.25 I34 111110100100 0.607 1.150 0.25 I44 011111100100 0.638 1.163 0.25 Finalizamos este desarrollo generando la población P5 . Individuo P5 x̃ f˜(x̃) P P (Ii ) = f˜i / f˜j I15 011111100100 0.638 1.163 0.25 I25 111110100100 0.607 1.150 0.25 I35 111110100100 0.607 1.150 0.25 I45 011011100100 0.630 1.160 0.25 27 Tras estas generaciones, la solución que se propone es ω = 011111100100 −→ x̃ = 0,638 ∈ [0, 4] −→ x = x̃ − 2 = −1,362 ∈ [−2, 2] tal que f (x) = f˜(x̃) = 1,163 es el valor máximo alcanzado. Con ayuda de MATLAB hemos representado la función f en el intervalo de definición [−2, 2] y hemos calculado el valor máximo de f en dicho intervalo. De modo que se tiene la siguiente gráfica Figura 2.1: tal que max{f (x) : x ∈ [−2, 2 ]} = 1,177 Con este ejemplo hemos intentado terminar de comprender el funcionamiento del algoritmo genético y al mismo tiempo ver el buen comportamiento del método. En este caso particular, en pocas iteraciones nos hemos aproximado al óptimo de la función mediante unas operaciones muy sencillas. Sin embargo, la convergencia al óptimo en este problema se podrı́a mejorar, pues hemos aplicado un proceso de selección elitista que mantenı́a los dos mejores individuos encontrados hasta ese momento para una población de tamaño tp = 4, resulta más interesante mantener en las nuevas poblaciones sólo al mejor individuo encontrado hasta ese momento con el fin de aumentar la diversidad de las poblaciones generadas. Nota 5 En la práctica, el tamaño de población, el número de iteraciones, la probabilidad de mutación... son determinados de forma experimental para cada problema concreto. 28 2.6. El Teorema de los Esquemas Uno de los resultados más importantes de la teorı́a de los algoritmos genéticos es el teorema de los esquemas. Este resultado justifica la convergencia del método usado por los algoritmos genéticos sobre un alfabeto binario. Antes de enunciar el teorema tenemos que introducir un nuevo concepto denominado esquema. Definición 12 Se llama esquema a una cadena que representa un conjunto de cadenas con similares caracterı́sticas, es decir, cadenas que tienen los mismos sı́mbolos en determinadas posiciones. El sı́mbolo ∗ se utiliza para indicar las posiciones donde no es obligada la coincidencia, es decir, indica las posiciones donde puede ir cualquier elemento del alfabeto. Ejemplo 12 Consideramos las cadenas 01001001 01000010 01101001 01100011 Es evidente que las posiciones 1, 2, 4 y 6 de todas estas cadenas coinciden. Por lo tanto, la cadena 01 ∗ 0 ∗ 0 ∗ ∗ es el esquema que las representa. Nota 6 Un mismo conjunto de cadenas puede pertenecer a distintos esquemas. Definición 13 Sea H un esquema. Se llama orden de H al número de sı́mbolos en H que son distintos de *. Se denotará por o(H). Se llama longitud de H al número de sı́mbolos del alfabeto { 0, 1, ∗ } más uno que hay entre la primera y la última de las posiciones distintas de *. Se denotará por δ(H). 29 Nota 7 Si el esquema H posee una única posición distinta de * entonces δ(H) = 0. Ejemplo 13 Sea el alfabeto binario A = { 0, 1}. Se tiene la siguiente tabla Esquema Orden Longitud H1 = 0 1 ∗ ∗ 1 ∗ 0 o(H1 ) = 4 δ(H1 ) = 6 H2 = ∗ 1 ∗ 1 ∗ 1∗ o(H2 ) = 3 δ(H2 ) = 4 H3 = 0 ∗ ∗ 0 1 0 1 o(H3 ) = 5 δ(H3 ) = 6 H4 = ∗ ∗ 0 ∗ 1 ∗ ∗ o(H4 ) = 2 δ(H4 ) = 2 H5 = ∗0 ∗ ∗ ∗ ∗∗ o(H5 ) = 1 δ(H5 ) = 0 H6 = ∗ ∗ ∗11 ∗ ∗ o(H6 ) = 2 δ(H6 ) = 1 Teorema 1 (Teorema de los Esquemas) Sea H un esquema. El número de individuos pertenecientes a H en la población k + 1 que resulta de aplicar los operadores genéticos (selección, cruce y mutación) sobre la población k puede aproximarse por la desigualdad m(H, k + 1) ≥ m(H, k) · δ(H) f (H) · [1 − pc · − o(H) · pm ] L−1 f¯ donde f (H) es el fitness medio de los individuos de m(H, k), f¯ es el fitness medio de la población Pk , pc es la probabilidad de cruce, pm es la probabilidad de que un bit mute, L es la longitud de los individuos y m(H, k) es el número de individuos pertenecientes al esquema H contenidos en la población k ésima. Demostración 1 Para obtener esta estimación tenemos que estudiar el efecto individual y combinado de los operadores genéticos sobre los esquemas contenidos en una población. 30 Sabemos que durante la selección la probabilidad de que un individuo Ii sea seleccionado viene dada por la expresión P (Ii ) = fi /Σfj . Luego, en n selecciones con reemplazamiento en la población Pk se obtiene que el número de individuos del esquema H que han sido escogidos en esas n selecciones viene dado por m(H, k + 1) = m(H, k) · n · f (H)/Σfj P Pero, como f¯ = fj /n denota el fitness medio de la población, podemos escribir la estimación anterior como sigue m(H, k + 1) = m(H, k) · f (H)/f¯ Por la tanto, el número de individuos pertenecientes al esquema H en la siguiente generación es directamente proporcional al fitness medio del esquema H e inversamente proporcional al fitness medio de la población. Esto quiere decir que los esquemas con fitness medio mayor que el fitness medio de la población aumentarán su número de individuos en la siguiente generación, y viceversa. De hecho, el crecimiento de los “buenos” esquemas será exponencial, mientras que los esquemas “malos” tienden a desaparecer. En particular, cuando el fitness medio de un determinado esquema H se mantiene por encima del fitness medio de la población una cantidad cf¯, entonces se tiene m(H, k + 1) = m(H, k) · f¯ + cf¯ = (1 + c) · m(H, k) f¯ Si suponemos un valor constante de c a lo largo de las distintas generaciones resulta que m(H, k) = m(H, 0) · (1 + c)k ∀k Por otro lado, resulta sencillo deducir que la probabilidad de supervivencia de un esquema H tras el proceso de cruce viene dada por ps = 1 − δ(H) L−1 Si se considera una probabilidad de cruce pc para cada par de individuos emparejados, entonces la probabilidad de supervivencia de un esquema H se aproxima por ps ≥ 1 − pc · δ(H) L−1 Ahora, combinando el efecto de la selección y del cruce sobre un esquema H, y teniendo en cuenta que ambas operaciones son independientes, se obtiene 31 · ¸ f (H) δ(H) m(H, k + 1) ≥ m(H, k) · ¯ · 1 − pc · L−1 f Es decir, el esquema H crece o decrece según un factor multiplicativo que, por ahora, depende de la calidad (su fitness medio) y de la longitud del esquema. Ası́, los esquemas con fitness medio por encima del fitness medio de la población y de longitud pequeña crecerán en la siguiente generación. Finalmente, estudiamos el efecto de la mutación sobre un esquema H. Es evidente que para que un esquema H sobreviva al proceso de mutación no deben cambiar ninguno de los sı́mbolos situados en las posiciones fijas, es decir, los sı́mbolos distintos de *. Por lo tanto, la probabilidad de que un esquema H sobreviva a una mutación viene dada por (1 − pm )o(H) donde o(H) es el número de posiciones fijas de H y (1 − pm ) es la probabilidad de que una posición fija no mute. En particular, para valores muy pequeños de pm la expresión anterior puede aproximarse por (1 − o(H) · pm ). Para concluir la demostración, tenemos que tener encuenta el efecto conjunto de los tres operadores genéticos sobre un esquema H, de modo que el número de individuos pertenecientes a H en la siguiente generación puede aproximarse mediante la siguiente desigualdad µ ¶ f (H) δ(H) m(H, k + 1) ≥ m(H, k) · ¯ · 1 − pc · · (1 − o(H) · pm ) L−1 f Si despreciamos los términos pequeños, obtenemos la desigualdad buscada ¶ µ f (H) δ(H) m(H, k + 1) ≥ m(H, k) · ¯ · 1 − pc · − o(H) · pm L−1 f 32 Ejemplo 14 Veamos con un sencillo ejemplo el efecto del cruce (en un punto) sobre los esquemas. Consideramos una cadena de longitud 7, ω = 0111000 Sean H1 y H2 dos esquemas de ω, H1 = ∗ 1 ∗ ∗ ∗ ∗ 0 H2 = ∗ ∗ ∗ 1 0 ∗ ∗ Suponemos que el punto de cruce es k = 3. H1 = ∗ 1 ∗ | ∗ ∗ ∗ 0 H2 = ∗ ∗ ∗ | 1 0 ∗ ∗ Observese que si el individuo que se cruza con ω no posee un 0 en la última posición o un 1 en la segunda posición, entonces el esquema H1 se destruye al aplicar el operador de cruce. Sin embargo, el esquema H2 sobrevive al proceso de cruce pues al menos uno de sus hijos pertenece a H2 . Si aplicamos el cruce en otros puntos, podemos concluir que el esquema H1 tiene mayor probabilidad de desaparecer pues existen muchos puntos que rompen la estructura que caracteriza dicho esquema. Es decir, existe una relación directa entre la desaparición de un esquema y lo que hemos definido como su longitud. En este caso concreto, se tiene que δ(H1 ) = 5 y δ(H2 ) = 1. Como el punto de cruce se elige aleatoriamente, podemos decir que las probabilidades de que dichos esquemas sean destruidos vienen dadas por δ(H1 )/(L − 1) = 5/6 δ(H2 )/(L − 1) = 1/6 En general, la probabilidad de supervivencia de un esquema H tras aplicarle el operador de cruce viene dada por 1− δ(H) L−1 33 2.7. Otros Algoritmos Genéticos: los Algoritmos Evolutivos Hasta ahora hemos estudiado el funcionamiento del algoritmo genético simple y hemos descrito cada uno de los operadores necesarios para su diseño. Estos operadores pueden redefinirse para adaptarlos a las necesidades concretas de cada problema, pero el algoritmo genético simple deberá de conservar su estructura interna: generación de una población inicial y generación de nuevas poblaciones mediante los operadores genéticos. Cualquier modificación que afecte a la estructura del algoritmo genético darı́a lugar a un algoritmo de búsqueda denominado algoritmo evolutivo. En general, se llama algoritmo evolutivo a cualquier algoritmo probabilı́stico que partiendo de una población inicial evoluciona, dando lugar a nuevas poblaciones, con la esperanza de que se hereden las buenas cualidades de las poblaciones anteriores. Por lo tanto, el algoritmo genético simple es un caso particular de algoritmo evolutivo donde los operadores de selección, cruce y mutación actúan sobre cada población para generar una nueva. Partiendo de un algoritmo genético simple vamos a definir algunas modificaciones que dan lugar a diferentes algoritmos evolutivos. Sólo mutación Si consideramos poblaciones de tamaño tp = 1, no tiene sentido definir el operador de cruce para generar nuevas poblaciones. En este caso, se tiene un algoritmo evolutivo que en cada generación muta el único individuo de la población. El nuevo individuo reemplazará al anterior si el fitness asociado es mejor. Información heurı́stica En muchas poblaciones es posible conocer algunas de las caracterı́sticas que deben tener las candidatas a solución. Por ejemplo, en algunos problemas se puede saber a priori que una cadena que sea solución del problema debe de empezar por uno. Esa información puede ser usada en la generación de la población inicial e incluso en los operadores genéticos. Convergencia Prematura Es posible evitar que una población converga a un valor que no nos permita mejorar para alcanzar la solución. Para ello, una posibilidad es inyectar de forma artificial 34 diversidad mediante la generación aleatoria de nuevos individuos que reemplacen parcial o totalmente la población actual. Búsqueda local En muchos algoritmos genéticos se introduce un nuevo operador que por si sólo constituye un algoritmo de búsqueda. Se trata de un proceso que busca puntos próximos a uno dado con la esperanza de encontrar el óptimo o, por lo menos, mejorar la solución. Existen muchas tecnicas para diseñar la búsqueda local, una de las más extendidas consiste en la modificación de los valores de los sı́mbolos que constituyen un individuo. Cada vez que se cambia el valor de un sı́mbolo se compara el nuevo individuo con el anterior, tomando el mejor de los dos. Podrı́amos repetirlo para todos los valores del individuo. Esta búsqueda local viene a ser un proceso de mutación dirigido. En general, la búsqueda local mejora considerablemente los algoritmos. Puede ser utilizada para mejorar la población inicial de modo que el algoritmo arranca con una población de óptimos locales. Incluso se puede aplicar a cada población generada. Ejemplo 15 (Sólo Muatación) Vamos a construir un algoritmo evolutivo para una población de tamaño tp = 1 que usa búsqueda local. Seudocódigo I0 ← generar población inicial I0 ← busqueda local (I0 ) I0 ← evaluar (I0 ) para k = 1 hasta NIter hacer I1 ← copiar (I0 ) I1 ← mutar (I1 ) I1 ← busqueda local(I1 ) E1 ← evaluar (I1 ) I0 ← mejor individuo (I0 , I1 ) fin para 35 Capı́tulo 3 El Problema de Satisfactibilidad 3.1. Introducción Una de las muchas aplicaciones de los algoritmos estudiados en el capı́tulo anterior es la resolución de los llamados problemas de satisfactibilidad o simplemente problemas SAT. A lo largo de este capı́tulo analizaremos algunos de los algoritmos evolutivos más importantes para la resolución de SAT. Empezamos introduciendo un poco de teorı́a relacionada con este problema. 3.2. Conceptos Básicos Definición 14 (Problema NP) Se dice que un problema P es No Determinı́stico Polinomial si existe un algoritmo no determinı́stico que lo resuelve en tiempo polinomial, es decir, dada una solución del problema P comprobar que efectivamente es solución lleva un tiempo polinomial. Usaremos la notación NP para referirnos a un problema No Determinı́stico Polinomial. Definición 15 Se dice que un problema P se reduce a otro problema Q si existe una función f computable que transforma entradas de P en entradas de Q y tal que x es solución de P si y sólo sı́ f (x) es solución de Q. En este caso, un algoritmo para resolver Q proporciona otro algoritmo para resolver P. Definición 16 (Problema NP duro) Se dice que un problema P es NP duro cuando cualquier otro problema NP se reduce a él. 36 Definición 17 (Problema NP completo) Se dice que un problema P es completo para NP o que es NP completo si es NP y, además, cualquier otro problema NP se reduce a él. Definición 18 (Variable booleana) Se dice que una variable es booleana cuando sólo puede tomar los valores verdadero o falso. El conjunto booleano viene dado por B = { falso, verdadero } = { 0, 1} Por lo tanto, si x es una variable booleana entonces el valor de x está en B. Definición 19 El problema de satisfactibilidad (SAT) consiste en encontrar un elemento x = (x1 , . . . , xn ) ∈ Bn tal que f (x) = 1 siendo f : Bn −→ B una función booleana dada. Si ∃ x ∈ Bn tal que f (x) = 1 entonces el problema se dirá satisfactible. En otro caso, se dirá insatisfactible. Proposición 1 El problema SAT es NP completo. Definición 20 Sea Ω un conjunto finito de variables, Ω = {x1 , . . . , xn }. Se dice literal sobre una variable xi a su afirmación o negación, denotándose respectivamente por xi o x̄i . Se llama cláusula a cualquier conjunto finito de disyunciones de literales sobre variables. Ejemplo 16 Dadas tres variables x1 , x2 y x3 , se pueden definir las siguientes cláusulas: C1 = x1 ∨ x2 ∨ x3 , formada por tres literales C2 = x1 ∨ x1 , formada por dos literales sobre la misma variable Definición 21 Se dice que una función booleana f : Bn −→ B está en forma normal si se expresa como una conjunción de cláusulas, es decir, si f (x) = C1 (x) ∧ C2 (x) ∧ · · · ∧ Cm (x) donde C1 , . . . , Cm son m cláusulas. 37 Ejemplo 17 (Un problema SAT) Dada la función booleana en forma normal f : B4 −→ B x −→ f (x) = C1 (x)∧C2 (x) donde C1 (x) = x1 ∨ x2 ∨ x3 y C2 (x) = x2 ∨ x3 ∨ x4 . ¿ ∃ x ∈ B4 tal que f (x) = 1 ? Si tomamos x = (0, 0, 1, 1) ∈ B4 entonces f (x) = C1 (x) ∧ C2 (x) = (0 ∨ 1 ∨ 0) ∧ (0 ∨ 1 ∨ 0) = 1 ∧ 1 = 1 Por lo tanto, este problema SAT es satisfactible. Nota 8 Cualquier función de un problema SAT puede expresarse en forma normal sin que esto suponga pérdida de generalidad. Por ello, toda función booleana asociada al problema SAT que se considere a partir de ahora estará expresada en forma normal. Definición 22 Un problema SAT se dice de clase k si todas las cláusulas contienen exactamente k literales distintos. Para simplificar la notación hablaremos del problema k SAT en lugar del problema SAT de clase k. Observaciones: 1. Mientras que los problemas 2 SAT son resolubles en tiempo polinomial, los problemas k SAT son NP completos para k ≥ 3. 2. Los algoritmos exactos pueden dar una respuesta definitiva (ser satisfactible o insatisfactible) a algunos problemas concretos, pero tienen una complejidad de tipo exponencial en el mejor de los casos. 3. Los algoritmos heurı́sticos, es decir, aquellos que aprovechan la información histórica, pueden encontrar soluciones para los problemas SAT, pero no garantizan una respuesta definitiva a todos los problemas, ya que no pueden determinar con seguridad si el problema es o no satisfactible. 38 3.3. Los Algoritmos Evolutivos para Resolver SAT Los algoritmos evolutivos son algoritmos heurı́sticos que han sido utilizados para resolver problemas SAT y otros muchos problemas NP completos. Como hemos visto en el capı́tulo anterior, el primer paso será la codificación de las variables del problema y la elección de la función fitness más adecuada para obtener una solución. A continuación describimos distintos métodos de codificación para el problema SAT. Respresentación cadena de bits El proceso inmediato y, hasta ahora, más seguro para representar una solución candidata de un problema SAT es una cadena de bits de longitud igual al número de variables, de manera que a cada variable se le asocia un bit. Para poder implementar el algoritmo genético tenemos que decidir cuál será nuestra función fitness. La función booleana f del problema SAT puede ser usada como función fitness pues las soluciones del problema SAT corresponden al óptimo global de f . Sin embargo, esta aproximación falla porque el algoritmo genético degenera en pura búsqueda aleatoria cuando todas las soluciones candidatas toman el valor cero en la función objetivo, a menos que una solución sea encontrada. Por este motivo se introduce la formulación MAXSAT, donde la función fitness representa el número de cláusulas que se verifican, es decir, fM AXSAT (x) = C1 (x) + · · · + Cm (x) siendo Ci (x) el valor de verdad de la i ésima cláusula, para i = 1, . . . , m. Ejemplo 18 Consideramos el problema SAT dado por la función f : B6 −→ B x −→ f (x) = C1 (x)∧C2 (x) donde C1 (x) = {x1 , x2 , x1 } y C2 (x) = {x3 , x4 , x6 }. Observamos que la cláusula C1 se verifica siempre, pues aparece x1 y x̄1 . Proponemos como solución el elemento x = (x1 , x2 , x3 , x4 , x5 , x6 ) = (1, 0, 0, 1, 1, 0) ∈ B6 39 La codificación de esta solución como cadena de bits es 100110. La función fitness que guiará el proceso de búsqueda se define como fM AXSAT (x) = C1 (x) + C2 (x) = 1 + C2 (x) ∀x El elemento que se propone como solución es en realidad solución del problema pues C2 (101010) = 1. Por lo tanto, fM AXSAT (101010) = 2 que es el valor máximo que puede tomar el fitness, es decir, se verifican todas las cláusulas. La función fM AXSAT es usada como función fitness en la mayorı́a de los algoritmos evolutivos para SAT, sin embargo, las dificultades que han ido apareciendo, incluso para resolver pequeños problemas SAT, han motivado la definición de nuevas funciones fitness que dependen de los mecanismos de adaptación utilizados, con el fin de lograr una distinción entre las distintas soluciones candidatas. Respresentación punto flotante En 1998, Bäck [3] propone transformar los problemas SAT en problemas de optimización continuos, de manera que la optimización numérica de estos problemas se pueda abordar mediante técnicas clásicas. Ası́, las soluciones candidatas son representadas por vectores continuos y ∈ [−1, 1]n y la función objetivo se define de modo que el óptimo global corresponde directamente a soluciones factibles para el problema SAT. Para ello, se reemplazan las variables xj por (yj − 1)2 y xj por (yj + 1)2 para cada j = 1, . . . , n. Además, se define la función fitness g(x) = m X i=1 siendo m el número de hij (x) = n Y hij (x) j=1 cláusulas y (yj − 1)2 si xj ∈ Ci (x) , x̄j 6∈ Ci (x) (yj + 1)2 si x̄j ∈ Ci (x) , xj 6∈ Ci (x) (yj − 1)2 · (yj + 1)2 si x̄j , xj ∈ Ci (x) 1 en otro caso 40 Ahora el objetivo es minimizar la función g. De hecho, valores de g nulos se corresponden con soluciones. Ejemplo 19 Consideramos el problema 3 SAT dado por la fórmula booleana f (x) = C1 (x) ∧ C2 (x) ∧ C3 (x) donde C1 (x) = x1 ∨ x2 ∨ x4 , C2 (x) = x1 ∨ x3 ∨ x4 , y C3 (x) = x2 ∨ x3 ∨ x4 . La función f : B4 −→ B se transforma en la función continua g : [−1, 1]4 −→ R definida por g(y) = (y1 −1)2 (y2 +1)2 (y4 −1)2 +(y1 +1)2 (y3 −1)2 (y4 +1)2 +(y2 +1)2 (y3 +1)2 (y4 −1)2 y nuestro objetivo es minimizar el valor de g. Ahora, los valores booleanos 0 y 1 son asociados a los valores -1 y 1. En la implementación del algoritmo, para comprobar si una solución para el problema SAT es realmente representada, se convierten los vectores continuos a -1 y 1, redondeando los valores negativos y positivos, respectivamente. A pesar de lo prometedora y original que resulta esta representación, los resultados obtenidos no mejoran los de la representación como cadena de bits. Respresentación clausal Propuesta por Hao [10] en 1995, esta representación resalta los efectos locales de las variables en las cláusulas. Para ello, se seleccionan asignaciones de valores localmente consistentes para las distintas variables en cada cláusula con el fin de encontrar asignaciones globalmente consistentes. Ejemplo 20 Volviendo a la función boolena del ejemplo anterior f (x) = C1 (x) ∧ C2 (x) ∧ C3 (x) donde C1 (x) = x1 ∨ x2 ∨ x4 , C2 (x) = x1 ∨ x3 ∨ x4 , y C3 (x) = x2 ∨ x3 ∨ x4 . Existen 8 asignaciones posibles para las variables en la primera cláusula, pero sólo una de ellas no es viable. En efecto, sı́ (x1 , x2 , x4 ) = (0, 1, 0) entonces C1 (x) = x1 ∨ x2 ∨ x4 = 0 ∨ 0 ∨ 0 = 0 41 En el resto de los casos, es fácil comprobar que C1 (x) = 1. Razonando de la misma forma, obtenemos las asignaciones posibles para las otras cláusulas. Una combinación de asignaciones posibles para las distintas cláusulas viene dada por {(x1 , x2 , x4 ) = (1, 0, 0); (x1 , x3 , x4 ) = (1, 1, 0); (x2 , x3 , x4 ) = (0, 1, 1)} Esta serı́a una candidata a solución, sin embargo, contiene asignaciones inconsistentes para la variable x4 , pues por un lado se tiene que x4 = 0 en la cláusula C1 y, por otro lado, x4 = 1 para la cláusula C3 . Por lo tanto, esta candidata a solución no es globalmente consistente. Siguiendo con este razonamiento, se llega a la candidata a solución {(x1 , x2 , x4 ) = (1, 0, 0); (x1 , x3 , x4 ) = (1, 1, 0); (x2 , x3 , x4 ) = (0, 1, 0)} que es globalmente consistente y, por lo tanto, x = (1, 0, 1, 0) es solución del problema. En este caso, el algoritmo genético tiene que ser guiado por una función objetivo que refleje la cantidad de inconsistencias entre las distintas asignaciones de variables para las cláusulas. Esto implica un proceso de búsqueda centrado en las relaciones entre las variables que, al mismo tiempo, están relacionadas entre sı́ por las distintas cláusulas. Aunque Hao argumentó que la eliminación de las asignaciones inviables reduce el espacio de búsqueda, la representación clausal induce un espacio de búsqueda mucho más grande incluso que el obtenido en la representación cadena de bits para problemas con muchas cláusulas. Respresentación “path” Sugerida por Gottlieb y Voss [6] en 1998, se basa en el hecho de que una solución viable debe satisfacer al menos una variable en cada cláusula. La idea consiste en ir seleccionando una variable en cada cláusula mediante un proceso que recorre todas las cláusulas exactamente una vez cada una. Si no hay inconsistencia en este proceso, podemos construir un vector de asignaciones. 42 Una función fitness razonable para este tipo de representación debe medir el número de inconsistencias. Ejemplo 21 Dado el problema SAT, encontrar x = (x1 , x2 , x3 , x4 ) ∈ B4 tal que f (x) = 1 siendo f (x) = (x1 ∨ x2 ∨ x4 ) ∧ (x1 ∨ x3 ∨ x4 ) ∧ (x2 ∨ x3 ∨ x4 ) El camino (x1 , x4 , x3 ) es factible y nos permite construir los vectores de asignación x = (1, 0, 0, 0) y x0 = (1, 1, 0, 0), mientras que el camino (x1 , x4 , x4 ) contiene una inconsistencia. Una caracterı́stica de este proceso, que lo distingue de la representación clausal, es que permite representar una familia de soluciones factibles en lugar de intentar determinar exactamente una. Aunque esta representación parece la más idónea, los resultados obtenidos no han mejorado los de la representación cadena de bits. Después de esta breve descripción podemos concluir que la mayorı́a de los algoritmos evolutivos para resolver problemas SAT utilizan como representación la cadena de bits. La diferencia se encuentra en el tipo de función fitness utilizada. Además, algunos algoritmos evolutivos incorporan otras caracterı́sticas, no genéticas, como la búsqueda local y la adaptación. A continuación describimos algunos de los algoritmos evolutivos más conocidos para SAT. SAWEA ( stepwise adaptation of weights ) En 1997, Eiben y van der Hauw [4] proponen una función fitness para resolver los problemas SAT, principalmente 3 SAT . El algoritmo evolutivo que la utiliza se denominó SAW y la función fitness se definió como fSAW (x) = ω1 · C1 (x) + · · · + ωm · Cm (x) donde los pesos ωi ∈ N, para i = 1, . . . , m, son adaptados para identificar las cláusulas difı́ciles de satisfacer en la actual fase de búsqueda. 43 En la primera fase todos los pesos son inicializados a uno, ωi = 1 para i = 1, . . . , m, con lo que se utiliza fM AXSAT . En las siguientes fases, cada 250 evaluaciones de la función fitness, los pesos son ajustados de acuerdo a la siguiente expresión ωi ← ωi + 1 − Ci (x∗ ) donde x∗ es el actual individuo mejor adaptado. De esta forma, se incrementan sólo los pesos que corresponden a cláusulas que no se verifican con x∗ , es decir, los pesos reflejan la “dureza” de las cláusulas asociadas obligando a enfocar hacia ellas la búsqueda evolutiva. En 1998, Bäck identifico las mejores caracterı́sticas de este algoritmo evolutivo y que se conoce por SAWEA. Estas caracterı́sticas son: tamaño de población tp = 1, un operador de mutación que mute exactamente un bit y un esquema de reemplazamiento en el cual de cada individuo se generan λ hijos mediante mutaciones, sustituyendo el mejor de todos ellos a su padre, este tipo de reemplazamiento se denota como (1, λ∗ ). Más tarde, Jong y Kosters sugieren aplicar un operador adicional a los individuos obtenidos de la mutación. Este operador selecciona aleatoriamente algunas cláusulas y, en aquellas que todavı́a no se verifiquen, muta una variable elgida aleatoriamente. Esta mejora se conoce por LSAWEA. RFEA ( refining functions ) En 1998, Gottlieb y Voss [7] introducen una nueva función fitness con el fin de distinguir las distintas cadenas binarias que tienen asociado un mismo valor fitness fM AXSAT . Para ello, definen una función r : Bn −→ [0, 1) que guarda el conocimiento heurı́stico adicional y utilizan la función fitness redefinida fREF (x) = C1 (x) + · · · + Cm (x) + α · r(x) para guiar el proceso de búsqueda. 44 En la función fREF la constante α > 0 controla la influencia de r. De esta forma, si usamos un nivel de influencia α ∈ [0, 1), podemos distinguir cadenas que satisfacen el mismo número de cláusulas. Una versión mejorada de este algoritmo evolutivo, denominada RFEA, usa una población de tamaño tp = 4, selección por torneo, y un esquema de reemplazamiento basado en la eliminación del peor individuo. Además, un nuevo individuo es rechazado si ya está en la población. El operador de mutación consiste en seleccionar una cláusula de entre las que no se verifican y cambiar el valor de una de las variables de la cláusula, elegida aleatoriamente. El principal componente del algoritmo evolutivo, junto con el operador de mutación, es la función de referencia n X K(xj )vj 1 j =1 r(x) = 1 + n X 2 1+ |vj | j =1 donde vj ∈ R es el peso asociado a la variable xj y K : B −→ {−1, 1} viene definida por K(0) = −1 y K(1) = 1. La adaptación de vj tiene como objeto escapar del óptimo local y se define como sigue vj ← vj − K(x∗j ) · |Uj (x∗ )| donde x∗ es el actual individuo mejor adaptado y |Uj (x∗ )| denota el cardinal del conjunto de cláusulas que no se satisfacen y que contienen la correspondiente variable x∗j . Existen otras formas de aproximar los pesos pero que no vamos a mencionarlas pues no serán de utilidad en nuestro trabajo. FlipGA ( Flip Heuristic ) Se trata de un algoritmo evolutivo con búsqueda local, desarrollado por Marchiori y Rossi [17] en 1999, que genera nuevos individuos mediante los operadores genéticos usuales y luego los mejora aplicando un proceso de búsqueda local. 45 Este algoritmo utiliza un tamaño de población tp = 10, proceso de selección proporcional al fitness, y un esquema de reemplazamiento generacional elitista, copiando los dos mejores individuos de la población actual en la población siguiente. Siempre se aplica cruce uniforme y un proceso de mutación con probabilidad 0,9, de modo que, cada bit del individuo que muta cambia de valor con probabilidad 0,5. La clave de este algoritmo es la aplicación de un proceso de búsqueda local a cada individuo después de realizar el cruce y la mutación. Este proceso consiste en recorrer de izquierda a derecha los bits de un individuo y cambiar el valor de cada bit seleccionado si el número de cláusulas que se satisfacen después del cambio menos el número de cláusulas que se satisfacen antes de cambiar el valor de dicho bit es mayor o igual que cero. Cuando se han considerado todos los bits del individuo, se mira si el fitness asociado a ese individuo ha mejorado, en ese caso, se repite el proceso. La idea de este algoritmo es conseguir explotación y exploración mediante dos módulos bien diferenciados: búsqueda local y operadores genéticos. De esta forma, se puede controlar mejor el efecto de los distintos módulos y modificarlos facilmente para la investigación experimental. ASAP ( Flip Heuristic and Adaptation ) Es una versión de FlipGA, introducida por Rossi [22] en 2000, denominada como algoritmo evolutivo adaptado para el problema de satisfactibilidad. Se obtiene de FlipGA al considerar un único individuo, esto es, tamaño de población tp = 1, un esquema de reemplazamiento, denotado por (1 + 1), en el cual de cada individuo se genera un hijo mediante mutación que sustituye al padre si es mejor y un mecanismo adaptado para controlar la diversificación en el proceso de búsqueda. ASAP actua sobre el individuo como sigue: primero, se aplica siempre el proceso de mutación y se cambia, para cada j ∈ {1 . . . n}, el valor del j ésimo bit con probabilidad µj ∈ [0, 0,5], donde µj es adaptado durante la ejecución. Luego, el nuevo individuo es mejorado mediante búsqueda local como en FlipGA. Además, un mecanismo de adaptación basado en la búsqueda tabu es usado para prohibir que cambie el valor de algunas variables y para controlar la probabilidad de mutación µj . En la siguiente tabla aparecen las principales caracterı́sticas de los algoritmos evolutivos, más conocidos, para resolver SAT. 46 Caracterı́sticas SAWEA RFEA FlipGA ASAP Reemplazamiento (1, λ∗ ) elimina el peor generacional (1 + 1) Selección — torneo proporcional al fitnes — fitness fSAW fREF fM AXSAT fM AXSAT Inicialización aleatorio aleatorio aleatorio aleatorio Cruce — — uniforme — Mutación muta uno muta uno aleatorio aleatorio adaptado Búsqueda local — — heurı́stico heurı́stico Adaptación fitness fitness — lista tabu Nota 9 Todos los algoritmos propuestos utilizan poblaciones iniciales puramente aleatorias. Por último, hablaremos de la búsqueda local para los problemas SAT. Con este proceso se pretende explorar el espacio de búsqueda de las asignaciones de valores para encontrar una solución que maximice el número de cláusulas que se verifican. Se empieza con una asignación generada de forma aleatoria y se van generando nuevas asignaciones al cambiar el valor de verdad de una variable simple. La fase crucial de este proceso es la selección de la variable que cambia. Existen muchas teorı́as y todas incluyen aleatoriedad y memoria. Uno de los más populares métodos de búsqueda local para SAT es WSAT. Fue desarrollado por Selman [23] en 1994 y McAllester [18] en 1997. En este caso, la selección de una variable para ser cambiada se realiza en dos etapas. Primero, una cláusula de entre las que no se verifican es elegida aleatoriamente. Luego, una de las variables de esta clausula se selecciona para cambiar su valor (pasa de cero a uno o de uno a cero) obteniendose una nueva asignación de valores. Para finalizar, observese que WSAT y FlipGA, y en consecuencia ASAP, adoptan diferentes estrategias de búsqueda. Mientras que una iteración de WSAT actua localmente sobre una cláusula que no se verifica, modificandola sin afectar al resto de cláusulas que si se verifican, en FlipGA, cada iteración actua globalmente sobre el problema con el fin de reducir el número total de cláusulas que no se verifican. 47 Capı́tulo 4 Sistemas de “Word Equations”: el Algoritmo de Makanin 4.1. Introducción A lo largo de este capı́tulo estudiaremos el tipo de problemas que pretendemos resolver con ayuda de la teorı́a desarrollada sobre los algoritmos genéticos. Introduciremos el concepto de sistema de “word equations” y algunos de los resultados conocidos hasta la fecha para su resolución. (ver [9, 21]) Pensemos en un sencillo problema que consiste en determinar si dos cadenas dadas sobre un alfabeto coinciden. No resulta difı́cil diseñar métodos capaces de resolver este problema. Ejemplo 22 Sea A = { a, b} un alfabeto. Consideramos las cadenas ω1 = abbabbab ω2 = abbabab Es evidente que ω1 6= ω2 . Una idea tan sencilla como la de recorrer ambas cadenas sı́mbolo a sı́mbolo (de forma simultánea) y comparar ambos sı́mbolos, nos permite diseñar un método que determina si ω1 coincide con ω2 . 48 ω1 - a b b a b b a ··· ω2 - a b b a b b b ··· ⇒ ω1 6= ω2 6 Un problema más difı́cil surge cuando se pretenden encontrar patrones en cadenas dadas. Pensemos en ecuaciones formadas por cadenas que contienen los sı́mbolos de un alfabeto dado, y las variables que representan los patrones a determinar. El caso más sencillo se dá cuando en un lado de la ecuación sólo hay sı́mbolos del alfabeto y las variables aparecen en el otro lado. Ejemplo 23 Tenemos que encontrar dos cadenas ω1 y ω2 sobre el alfabeto A = { a, b} tal que al reemplazar x por ω1 e y por ω2 en la ecuación xabbxy = aabbaabaaa ambos lados de la igualdad sean idénticos. De nuevo, existen muchas formas de calcular ω1 y ω2 . En este caso, es evidente que ω1 = a y ω2 = abaaa son solución de la ecuación. Pero, no tiene por que ser la única posibilidad. Nota 10 La búsqueda de patrones en ecuaciones donde uno de los lados es constante (formado por la cadena texto) y en el otro aparecen los patrones a encontrar (representados por variables) ha sido muy estudiado y existen algoritmos bastante eficientes que lo resuelven. En general, podemos plantear el problema de encontrar la solución de una ecuación formada por cadenas de un alfabeto y tal que en ambos lados aparecen variables a determinar. Como veremos, la resolución de estos problemas no es tan sencilla. Ejemplo 24 Determinar la solución de la ecuación xaxbya = bybyax no resulta una tarea fácil. Soluciones parciales de este problema se conocen desde hace tiempo. En 1972, Lentin [15], Plotkin [20] y Siekmann [25] diseñaron procedimientos de semi decisión, es decir, algoritmos que encuentran una solución si existe alguna pero que podrı́an no terminar si no existe 49 solución. En 1971 Hmelevski [11] resuelve el problema para ecuaciones con tres variables. Finalmente, en 1977 Makanin [16] resuelve el problema mediante el primer algoritmo (y único conocido hasta la fecha) que encuentra solución si existe alguna y además es capaz de determinar la no existencia de solución. Los documentos originales de Makanin estaban enfocados a determinar si la existencia de solución de este tipo de ecuaciones, denominadas “word equation”, es un problema decidible. No estaba interesado en la complejidad o en la implementación del algoritmo. Aunque posteriormente se han realizado mejoras (Pécuchet [19], Abdulrab [1], Jaffar [13], Schuld [24], Koscielski, Pachóski [14], entre otros, consiguieron simplificar algunos de los detalles técnicos de la demostración, empezaron a aproximar el problema desde un punto de vista computacional y realizaron un estudio sistemático de su complejidad) el algoritmo de Makanin sigue siendo inviable en la práctica. El desarrollo teórico del algoritmo de Makanin que aparece en este documento nos permitirá comprender su funcionamiento y nos dará una idea de su complejidad computacional. 4.2. Conceptos Básicos Sean A = { a1 , . . . , ar } un alfabeto y V = { v1 , v2 , . . .} un conjunto infinito de variables tal que A ∩ V = ∅. Definición 23 Se llama “word equation” al par (L, R) ∈ (A ∪ Ω)∗ × (A ∪ Ω)∗ donde Ω ⊆ V es un conjunto finito (de variables). Se denotará por L = R. Definición 24 Se llama longitud de la ecuación E ≡ (L, R) al número dado por |E| = |L| + |R|. Nota 11 Aunque V es un conjunto infinito de variables, en una ecuación E aparecen sólo un número finito de variables que, en genral, denotaremos por Ω = {x1 , . . . , xn } ⊆ V. 50 Definición 25 Se llama sistema de “word equations” a cualquier conjunto de ecuaciones { L1 = R1 , . . . , Lk = Rk } donde (Li , Ri ) ∈ (A ∪ Ω)∗ × (A ∪ Ω)∗ para cada i = 1, . . . , k. Definición 26 Un homomorfismo σ : (A ∪ Ω)∗ −→ A∗ se dice solución de un sistema de “word equations” cuando deja invariantes los elementos del alfabeto A y además σ(Li ) = σ(Ri ) para todo i = 1, . . . , k. Definición 27 Una solución σ de un sistema se dice mı́nimal si la suma X |σ(x)| es mı́nima en el x∈ Ω conjunto de longitudes de las soluciones. Definición 28 Dado un sistema de “word equations” sobre un alfabeto A, de conjunto de variables Ω = {x1 , . . . , xn }, se llama exponente de periodicidad de una solución σ al máximo exponente de periodicidad de las cadenas σ(xi ) para i = 1, . . . , n. 4.3. 4.3.1. El Algoritmo de Makanin Representación Gráfica Será muy importante comprender la siguiente representación de las “word equations” para justificar los conceptos utilizados en el algoritmo de Makanin, ası́ como para entender su desarrollo. Esta representación es el punto de arranque de dicho algoritmo. Consideramos la ecuación xaby = ybax sobre el alfabeto A = {a, b} y donde x e y representan las variables. Gráficamente, la cadena xaby será representada por 51 x b a y siendo la longitud de las lineas horizontales desconocida en cada caso, excepto, las correspondientes a las constantes que son siempre de longitud uno. Las lineas verticales se llamaran fronteras. La ecuación posee solución si existe una forma consistente de superponer ambos lados de la igualdad de manera que los trozos (representados mediante segmentos horizontales) entre las fronteras coincidan. En general, existen muchas posibilidades para hacer las superposiciones. Para la ecuación dada xaby = ybax se tienen por ejemplo estas dos posibilidades, entre muchas otras, x b x a b y y a a b y x y a b x Nota 12 Dibujamos los sı́mbolos en distintos niveles para destacar las fronteras de cada uno. En la siguiente etapa, se eliminan los sı́mbolos que coinciden desde la izquierda hasta la derecha. Por ejemplo, en la representación x b a y y a b 52 x podemos reemplazar y = xa en el otro suceso de y, para obtener b a b x a x =⇒ =⇒ a b xa = ax a x b x La idea básica del funcionamiento del algoritmo de Makanin es como sigue: 1. Suponer un orden de fronteras, es decir, cuál viene primero, cuál es la segunda, . . . para todas las fronteras iniciales de ambos lados de la ecuación. 2. Proceder de izquierda a derecha reemplazando igualdades por igualdades. Sin embargo, este ingenioso método tiene algunos inconvenientes: El número de veces que aparece una variable puede aumentar después del reemplazamiento. No siempre son evidentes los reemplazamientos. En el siguiente ejemplo, x b a y y a b x no existe un reemplazamiento evidente. Este proceso puede entrar en un bucle infinito. En uno de los ejemplos anteriores, se llega a ax = xa. 53 Por estos motivos es necesario un estudio más elaborado de la representación. Empezamos construyendo, para cada “word equation”, una representación como la anterior. Es conveniente, para evitar que el número de ocurrencias de una variable aumente con los reemplazamientos, trabajar con un sistema equivalente de ecuaciones en el cual cada variable aparece no más de dos veces en cada ecuación. Nota 13 Siempre es posible pasar de un sistema de “word equations” dado a otro equivalente donde todas las variables ocurren a lo sumo dos veces. Ejemplo 25 Sea la ecuación bxyx = yaxz. Las variables de esta ecuación son x que ocurre tres veces y que ocurre dos veces z que ocurre una vez Construimos el sistema equivalente bx1 yx1 = yax2 z x1 = x2 para evitar que la variable x aparezca tres veces. Ahora, todas las variables suceden no más de dos veces en cada ecuación. Una posible representación de este sistema equivalente, con fronteras ordenadas, vendrı́a dada como sigue: b y x1 bx1 yx1 = yax2 z x1 x1 =⇒ y x1 = x2 x2 a =⇒ x2 z Notar que ambas ecuaciones pueden ser representada en la misma gráfica de la siguiente forma: 54 b y x1 x1 x2 y x2 a 1 2 3 z 4 5 6 7 Además, resulta conveniente tener exactamente dos copias de cada variable en la representación. En este caso, tenemos que añadir una copia de la variable z. Ası́, la representación final del sistema viene dada por b y x1 x1 x2 z y x2 a 1 2 3 z 4 5 6 7 Esta representación es el punto de arranque del algoritmo de Makanin. 4.3.2. Las Ecuaciones Generalizadas Introducimos los conceptos utilizados por Makanin [16, 13, 24] para poder comprender dicho algoritmo. Definición 29 Sea (BD, ≤) un conjunto finito de números naturales que denominaremos conjunto de fronteras. Se llama base a una tupla bs = (t, (e1 , . . . , en )) donde n ≥ 2, t ∈ A ∪ Ω y Ebs = (e1 , . . . , en ) es una secuencia de fronteras ordenados por ≤. BS denotará un conjunto finito de bases. 55 Definición 30 Una base bs = (t, Ebs ) ∈ BS se dice constante si t ∈ A. En otro caso, es decir, si t ∈ Ω se dirá base variable. Definición 31 Sea bs ∈ BS, cualquiera. Se llama frontera izquierda de la base bs al primer elemento en Ebs . Se denotará Izq(bs). Se llama frontera derecha de la base bs al último elemento en Ebs . Se denotará Der(bs). Nota 14 Las letras i, j,. . . se usarán para denotar las fronteras. Ejemplo 26 Sea BD = {1, 2, 3, 4, 5, 6}, A = {a, b} y Ω = {x, y, z}. Consideramos las bases bs1 = (a, (1, 3, 5, 7)) bs2 = (y, (1, 2, 4, 6)) Resulta que bs1 es una base constante con Izq(bs1 ) = 1 y Der(bs1 ) = 7, mientras que bs2 es una base variable con Izq(bs2 ) = 1 y Der(bs2 ) = 6. Definición 32 Se llama ecuación generalizada (EG) a la tupla (A, Ω, BD, BS) tal que 1. Para cada x ∈ Ω hay exactamente dos bases, llamadas duales y denotadas por x y x̄, respectivamente. Además, las secuencias de fronteras asociadas, Ex y Ex̄ , deben tener la misma longitud. 2. La secuencia de fronteras de cada base constante tiene exactamente dos elementos que son consecutivos en el orden ≤. Ejemplo 27 Sea A = {a, b} y Ω = {x, y}. Se tiene la siguiente ecuación generalizada: 56 x y a b b a y 1 2 3 x 4 5 6 7 siendo el conjunto de fronteras BD = {1, 2, 3, 4, 5, 6, 7} y el conjunto de bases BS = {(x, (1, 3)), (x, (5, 7)), (y, (4, 6)), (y, (2, 4)), (a, (3, 4)), (a, (4, 5)), (b, (6, 7)), (b, (1, 2))}. Definición 33 Se llama columna de una EG a un par (i, j) de fronteras tal que i ≤ j. Para todo i, la columna (i, i) se dice vacı́a y la columna (i, i+1) se dice indescomponible. Dada una base bs, cualquiera. Se define col(bs) = (Izq(bs), Der(bs)) Por lo tanto, una base es vacı́a si su columna es vacı́a. Definición 34 Una EG se dice que está resuelta si todas sus bases variables son vacı́as. Definición 35 Se llama unificador de una EG a una función σ que asigna a cada columna indescomponible de la EG una cadena ω ∈ A ∪ Ω (extendida por concatenación a todas las columnas no vacı́as de la EG) verificando las siguientes propiedades: 1. Para cada base constante bs = (a, Ebs ), σ(col(bs)) = a 2. Para todo par de variables duales, x y x̄, y para todo elemento ej ∈ Ex , σ(e1 , ej ) = σ(ē1 , ēj ) En particular, σ(col(x)) = σ(col(x̄)). Nota 15 Si ej ∈ Ex entonces ēj ∈ Ex̄ . 57 Veamos algunas de las propiedades que verifica el unificador σ de una EG. Si σ(i, i + 1) es no vacı́o para todo i ∈ BD entonces σ se dirá unificador estricto. |σ(b1 , bm )|, donde b1 y bm denotan el primer y último elemento de BD, respectivamente, es el ı́ndice del unificador σ. El exponente de periocidad σ es el máximo exponente de periocidad de las cadenas σ(col(x)), donde x es una variable base. Para comprender la complejidad de resolver las ecuaciones generalizadas, veremos que el problema de resolución de sistemas de ecuaciones lineales diofánticas, que es NPcompleto, se reduce a resolver una EG. Por lo tanto, encontrar solución de una EG es un problema NP duro. Definición 36 Dada una EG. Para cada constante a ∈ A se define el sistema asociado de ecuaciones lineales diofánticas, L(GE, a), dado de la siguiente forma: 1. para cada columna indescomponible (i, i+1) de la EG introducimos una variable zi . 2. para cada par de bases de variables duales (x, (e1 , . . . , en ) y (x, (ē1 , . . . , ēn ) construimos (n-1) ecuaciones dadas por X ej ≤i<ej+1 X zi = zi j = 1,. . . ,n-1 ēj ≤i<ēj+1 3. para cada base constante (t,(i,i+1)) construimos la ecuación 1 si t = a zi = 0 si t 6= a Lema 1 Si existe unificador de la EG entonces el sistema asociado L(EG, a) es resoluble para cada constante a ∈ A. Sabemos que la resolución de los sistemas lineales diofánticos es un problema decidible pero en cualquier caso NP completo. 58 Definición 37 Una EG se dice que es admisible si su sistema asociado L(EG, a) es resoluble para cada a ∈ A. Para finalizar esta sección veremos que cualquier “word equation” se puede escribir como un conjunto finito de ecuaciones generalizadas. Lema 2 Dada E una “word equation”, existe un algoritmo GEN que proporciona un conjunto finito de ecuaciones generalizadas GEN(E) verificando las siguientes propiedades: 1. E tiene un unificador con exponente de periocidad p sı́ y sólo si alguna EG ∈ GEN (E) posee un unificador estricto con exponente de periocidad p. 2. Para cada EG ∈ GEN (E), toda frontera es la frontera derecha o izquierda de una base. Además, toda secuencia de fronteras contiene exactamente a estas dos fronteras. 3. Para cada EG ∈ GEN (E) el número de bases de la EG no supera 2|E|. 4. Toda EG ∈ GEN (E) es admisible. Ejemplo 28 Sean A = {a, b} y Ω = {x, y, z}. La “word equation” E ≡ (bxyx, yaxz) tiene asociado el conjunto finito de ecuaciones generalizadas GEN(E). Antes de determinar este conjunto GEN(E) notar que es conveniente trabajar con un sistema equivalente de ecuaciones en el que cada variable ocurre no más de dos veces en cada ecuación. Esto siempre es posible, basta con redefinir las variables. En este caso, la ecuación E es equivalente al sistema bx1 yx1 = yax2 z x1 = x2 donde cada variable ocurre a lo sumo dos veces. Por lo tanto, una EG viene dada por A = {a, b} Ω̃ = {x1 , x2 , y, z} 59 BD = {1, . . . , 7} BS = {(b, (1, 2)), (a, (3, 4)), (x1 , (2, 3)), (x1 , (5, 7)), (y, (3, 5)), (y, (1, 3)), (x2 , (4, 6)), (x2 (5, 7)), (z, (6, 7)), (z, (6, 7))} Gráficamente se representa como sigue: b y x1 x1 x2 z y x2 a 1 4.3.3. 2 3 z 4 5 6 7 EL Algoritmo de Transformación Como toda “word equation” E posee un conjunto de ecuaciones generalizadas GEN(E) equivalente, podemos reducir nuestro problema al estudio de la resolución de ecuaciones generalizadas. Dada una EG la idea del algoritmo de transformación es ir reemplazando sucesivamente, de izquierda a derecha, las variables iguales. Se empieza con la variable situada más a la izquierda y más grande, que se denomina portadora, y se transporta toda su columna a la posición de su dual. Sin embargo, no siempre es posible mover todas las columnas sin perder parte de la información esencial. Ejemplo 29 Consideramos la representación de la ecuación xaby = ybax dada por x b a y y a b 1 2 x 3 4 5 60 6 7 8 El conjunto de bases viene dado por BS = {(x, (1, 5)), (x, (4, 8)), (y, (7, 8)), (y, (1, 2)), (a, (5, 6)), (a, (3, 4)), (b, (2, 3)), (b, (6, 7))} Las variables situadas más a la izquierda son x e y, pero la más grande es x. Ası́ que, tomamos x como variable portadora y al intentar trasladar toda su columna, (1, 5), a la posición de su dual nos encontramos con dificultades en cuanto a la identificación de nuevas fronteras. Dichas dificultades motivan las siguientes definiciones: Definición 38 Se llama portadora de la EG a la base de variable no vacı́a con frontera izquierda más pequeña. Si hay más de una, se toma la que tiene mayor frontera derecha. Si todavı́a hay más de una candidata, se elige una al azar entre estas. Se denotará por xc la portadora de la EG. Sus fronteras serán lc = Izq(xc ) y rc = Der(xc ). Se define la frontera crı́tica de la EG como sigue min{Izq(y) : rc ∈ col(y)} si {Izq(y) : rc ∈ col(y)} 6= ∅ cr = r si {Izq(y) : rc ∈ col(y)} = ∅ c Definición 39 Dada bs una base cualquiera de EG tal que bs no es portadora. Se dice que bs es superfluo si col(bs) = (i, i) < lc . Se dice que bs es transporte si lc ≤ Izq(bs) < cr o bien col(bs) = (cr , cr ). En otro caso, la base bs se dice fija. Nota 16 Todas las bases variables con Izq(x) < lc son vacias como consecuencia de la definición de base portadora. Además, cada base, excepto la portadora, es exactamente superflua, transporte o fija, dependiendo de la región que ocupe por debajo de su frontera derecha en el diagrama. b1 lc superfluo transporte (col(b1 ) < lc ) (lc ≤ Izq(bs) < cr ) 61 cr rc fijo fijo bm Ejemplo 30 1. En este diagrama x b a y y a b 1 2 3 x 4 5 6 la variable portadora es y. Para esta variable, lc = 1 y rc = 3. Por lo tanto, {Izq(y) : rc ∈ col(y)} = {Izq(y) : 3 ∈ col(y)} = ∅ ⇒ cr = rc = 3, es decir, la frontera crı́tica de esta EG es cr = 3. 2. En el siguiente diagrama x b a y y a b 1 2 x 3 4 5 6 7 8 la variable portadora es x. Para esta variable, lc = 1 y rc = 5. Por lo tanto, {Izq(y) : rc ∈ col(y)} = {Izq(y) : 5 ∈ col(y)} = {Izq(x) : 5 ∈ col(x) = (4, 8)} = 4 es decir, la frontera crı́tica de esta EG es cr = 4. Hemos obtenido un método para determinar las bases que deberı́amos mover, las bases transporte. Ahora, vamos a estudiar hacı́a donde se deben trasladar estas bases. Definición 40 Se llama impresión de una EG a una ordenación lineal ≤ en el conjunto BD ∪ {itr : i ∈ [lc , rc ]} 62 verificando las siguientes propiedades: 1. ≤ extiende el orden de BD y además j tr < k tr para lc ≤ j < k ≤ rc . 2. tr(Ec ) = Ēc , es decir, la estructura de la portadora superpone a su dual. 3. Si x es transporte y x̄ es fijo, entonces se cumple etr i = ēi para algún ei ∈ Ex ⇒ tr(Ex ) = Ēx Es decir, el orden ≤ es consistente con la información de secuencias de fronteras. 4. Si (c, (i, j)) es una base constante, entonces i, j son consecutivos en el orden ≤. Además, itr y j tr tambien son consecutivos cuando i, j ∈ [lc , rc ]. Es decir, las constantes se conservan. Nota 17 Para cada frontera i ∈ [lc , rc ] denotamos por itr al nuevo lugar que ocupa la frontera i tras el transporte. En general, tr tr(Ex ) = tr(e1 , · · · , en ) = (etr 1 , · · · , en ) Una vez realizada la clasificación de las bases y una impresión de la EG, es decir, una guı́a de a donde vá cada frontera transportada, se dejan las bases fijas (intactas) y se mueven todas las bases transporte. De esta forma estamos reemplazando igualdades por igualdades de izquierda a derecha. Por lo tanto, el algoritmo de Makanin se guı́a mediante la variable portadora y su dual. Ejemplo 31 Consideramos la siguiente EG: u x̄c xc ū y ȳ 1 2 3 4 5 6 7 8 donde BS = {(u, (1, 3)), (ū, (6, 8)), (xc , (1, 5)), (x̄c , (5, 8)), (y, (3, 5, 7)), (ȳ, (2, 4, 5))} 63 xc es la variable portadora tal que lc = 1 y rc = 5. Calculamos la frontera crı́tica, cr = min{Izq(y) : rc ∈ col(y)} = 3 Por lo tanto, lc cr rc u x̄c xc ū y ȳ 1 2 3 4 5 6 7 8 Veamos como se transportan las variables transporte (ver [9]): Como cr < rc , entonces xc y x̄c se reducen de la siguiente forma, Ec = Ec ∩ {i ∈ BD : cr = 3 ≤ i} = {3, 4, 5} Ēc = Ēc ∩ {i ∈ BD : ctr r = 7 ≤ i} = {7, 8} Hemos obtenido las nuevas bases de la variable portadora y de su dual. lc cr rc u x̄c xc ū y ȳ 1 2 3 4 5 6 7 8 Recordar que si cr = rc entonces Ec = tr(Ec ), es decir, en este caso xc es transportada completamente. Estudiamos el comportamiento de las bases transporte bs, Ec = Ec ∪ {i : i ∈ Ebs y cr = 3 < i} = {3, 4, 5} ∪ {4} = {3, 4, 5} Ēc = Ēc ∪ {itr : i ∈ Ebs y cr < i} = {7, 8} ∪ {4tr } = {7, 4tr , 8} Hemos introducido una nueva frontera, 4tr . Notar que 1tr = 5 y 5tr = 8. 64 lc cr rc u x̄c xc ū y ȳ 1 2 3 4 5 6 4tr 8 7 Ya sólo queda por transportar u y ȳ. Como las variable y y su dual tienen un segmento en común, se pierde parte de la información al transportar ȳ. Teniendo en cuenta que 1tr = 5, 2tr = 6, 3tr = 7 obtenemos la gráfica lc cr rc x̄c u xc ū y ȳ 1 2 3 4 5 6 7 4tr 8 Ahora, podemos eliminar las fronteras inferiores a la frontera crı́tica. Definición 41 (El Árbol de Makanin) Dada E una “word equation”, se define recursivamente el árbol de Makanin T (E) asociado a E como sigue: 1. La raiz de T (E) es E. 2. Los hijos de E son GEN (E). 3. Para cada nodo EG (distinto de la raiz), el conjunto de sus hijos es un número finito de ecuaciones generalizadas, T RAN S(EG). 65 Teorema 2 Sea E una “word equation”. E tiene un unificador si y sólo si T (E) tiene un nodo etiquetado con una ecuación generalizada resoluble. Este teorema proporciona el siguiente procedimiento de semi decisión: Examinar todos los nodos del árbol T (E) para averiguar si E tiene solución. Nota 18 En general, el árbol de Makanin asociado a una “word equation” puede ser infinito. Sin embargo está demostrado que existe un número finito kE que acota el número de nodos que hay que visitar. Si no se encuentra solución tras la visita de dichos kE nodos, se puede concluir que no existe solución. kE es de carácter doblemente exponencial en la longitud de la ecuación E. 4.4. Casos Particulares de Sistemas de “Word Equations” Para finalizar este capı́tulo vamos a exponer algunos resultados conocidos para casos particular de sistemas de “word equations”. Definición 42 Un sistema de “word equations” se dice cuadrático si cada variable aparece a lo sumo dos veces en el sistema. Hablaremos de sistemas cuadráticos para referirnos a los sistemas de “word equations” de tipo cuadrático. El algoritmo más sencillo que los resuelve usa un espacio lineal no determinı́stico. En la primera etapa se comprueba qué variables pueden ser reemplazadas por la palabra vacı́a, de modo que podemos suponer que las ecuaciones son de la forma x... = y... donde y es una variable tal que x 6= y. Además, si asumimos que x es un prefijo de y podemos reemplazar y (al menos dos veces) por xy, y cancelar x en la parte izquierda de la primera ecuación. Ahora, comprobamos si y puede ser sustituido por la palabra vacia, Λ. 66 Repetimos el proceso sucesivamente. Aunque el tamaño del sistema cuadrático nunca decrece, la longitud de una solución mı́nima decrece en cada etapa. Por lo tanto, el algoritmo no determinı́stico encontrará una solución, si la hay. Teorema 3 Sea |A| ≥ 2. El problema de decidir la existencia de solución de una “word equation” cuadrática es NP duro. Demostración 2 Consideramos un problema 3 SAT dado por la función F = C0 ∧ · · · ∧ Cm−1 donde cada cláusula tiene la forma Ci = (X̃3i ∨ X̃3i+1 ∨ X̃3i+2 ) para la familia {X̃j : j = 0, . . . , 3m − 1}. Sea V el conjunto de variables del problema. Podemos reescribir este problema como un sistema de “word equations” introduciendo nuevas variables ci , di para i = 0, . . . , m − 1 xj para j = 0, . . . , 3m − 1 yv , zv para cada variable v ∈ V Tomamos las constantes a y b del alfabeto A tal que a 6= b. Para cada cláusula Ci definimos dos ecuaciones: ci x3i x3i+1 x3i+2 = a3m ci di = a3m−1 Para cada v ∈ V consideramos el conjunto de posiciones {i1 , . . . , ik } tal que v = X̃i1 = · · · = X̃ik 67 y el conjunto de posiciones {j1 , . . . , jn } talque ṽ = X̃j1 = · · · = X̃jn Suponemos que k ≤ n; el caso n ≤ k es simétrico. Para cada v definimos dos nuevas ecuaciones: yv zv = b xi1 · · · xik yv an bxj1 · · · xjn zv = an ban b Por lo tanto, la fórmula F es satisfactible si y sólo si el sistema cuadrático ci x3i x3i+1 x3i+2 = a3m ci di = a3m−1 yv zv = b xi1 · · · xik yv an bxj1 · · · xjn zv = an ban b tiene solución. Pero, cualquier sistema de k “word equations”, con k ≥ 1 L1 = R1 , . . . , Lk = Rk donde R1 , . . . , Rk ∈ {a, b}∗ , es equivalente a una ecuación de la forma L1 c · · · Lk−1 cLk = R1 c · · · Rk−1 cRk siendo c una constante. Luego, podemos reescribir el sistema cuadrático como una “word equation” sin más que aumentar el tamaño del alfabeto en una constante. Incluso, podemos eliminar esta nueva constante c, sin incrementar el número de veces que aparece cada variable del sistema, codificando las tres letras como aba, abba y abbba y reemplazando cada variable xi del problema por axi a. Por lo tanto, hemos reducido un problema 3 SAT , cualquiera, a una “word equation” cuadrática y sabemos que 3 SAT es NP completo, es decir, hemos probado que el problema de existencia de solución de una “word equation” cuadrática sobre un alfabeto binario es NP duro. 68 Destacar que es posible reducir un problema de tipo 3 SAT a un sistema de “word equations”, de forma análoga al esquema utilizado en la anterior demostración. Proposición 2 Todo problema 3 SAT se reduce a un sistema de “word equations” sobre el alfabeto unario A = {1}. Demostración 3 Consideramos un problema 3 SAT dado por la función F = C0 ∧ · · · ∧ Cm−1 donde cada cláusula tiene la forma Ci = X̃3i ∨ X̃3i+1 ∨ X̃3i+2 para la familia de literales {X̃j : j = 0, . . . , 3m − 1}. Sea V el conjunto de variables del problema. Podemos reescribir este problema como un sistema de “word equations” sobre el alfabeto unario A = {1} introduciendo nuevas variables: ci , di yv , zv para i = 0, . . . , m − 1 para cada variable v ∈ V Para cada v ∈ V definimos la ecuación: yv zv = 1 Para cada cláusula Ci = X̃3i ∨ X̃3i+1 ∨ X̃3i+2 definimos dos ecuaciones: ci v3i v3i+1 v3i+2 = 111 ci di = 11 donde yv vj = z si X̃j = v si X̃j = ṽ v En estas condiciones se puede ver que la fórmula F es satisfactible si y sólo si el sistema formado por las ecuaciones ci v3i v3i+1 v3i+2 = 111 69 ci di = 11 yv zv = 1 para i = 0, . . . , m − 1 y v ∈ V tiene solución. Por último, consideramos un problema particular de sistema de “word equations” que se obtiene al imponer restricciones en la longitud de las soluciones. Definición 43 Dado un sistema S de n “word equations” sobre un alfabeto A y con variables en el conjunto Ω = {x1 , . . . , xm }. Se llama l sistema de “word equations” al problema de determinar si existe una solución de S σ : (A ∪ Ω)∗ −→ A∗ tal que |σ(xi )| ≤ l, para cada i = 1, . . . , m. Esta problema se denotará por l SW ES. Proposición 3 El problema l SW ES es NP completo para l ≥ 2. Demostración 4 Es fácil ver que l SW ES está en NP. Tomando como cota l = 2, se obtiene el resultado de forma análoga a como se probó la proposición 2, pero en este caso se pueden suprimir del sistema equivalente a F las ecuaciones de la forma ci di = 11 para cada i = 0, . . . , m − 1. En consecuencia 3 SAT se reduce ha 2 SW ES. Nota 19 Nuestro trabajo se centrará en la búsqueda de solución de los problemas l SW ES sobre el alfabeto binario A = {0, 1}. 70 Capı́tulo 5 Un Algoritmo Genético Simple para Resolver l SW ES 5.1. Introducción Una vez estudiado el problema de encontrar solución de un sistema de “word equations” y tras observar la enorme complejidad (triplemente exponencial) del algoritmo de Makanin, intentaremos construir un algoritmo genético que proporcione una solución si existe. La motivación de este trabajo son los buenos resultados que se han obtenido en la resolución de los problemas SAT mediante el uso de los algoritmos evolutivos y el hecho de que los problemas SAT se reducen a sistemas de “word equations”, como hemos visto. Empezamos nuestro trabajo diseñando un algoritmo genético simple con la esperanza de obtener resultados más o menos satisfactorios. 5.2. Codificación de los Individuos Sea A = {0, 1} el alfabeto binario y Ω = {x1 , . . . , xm } el conjunto de variables del problema l SW ES formado por n “word equations” S = { L1 = R1 , . . . , Ln = Rn } siendo l una cota para la longitud de las variables. Recordar que un homomorfismo σ es una solución candidata de S si |σ(xi )| ≤ l para cada i = 1, . . . , m. Por lo tanto, tenemos que codificar m variables como cadenas de 71 longuitud menor o igual que l sobre el alfabeto A. Definimos un nuevo sı́mbolo B, que llamaremos sı́mbolo blanco, de modo que, todas las variables del problema l SW ES se pueden codificar como cadenas de longitud exactamente igual a l sobre el nuevo alfabeto B = {0, 1, B} de la siguiente forma: para cada i = 1, . . . , m, sea αi la codificación de la variable xi sobre B, esta codificación es una cadena formada por sı́mbolos de B que se lee de izquierda a derecha y cada vez que se encuentra el sı́mbolo blanco B se pasa al siguiente sı́mbolo sin hacer nada. Ejemplo 32 Las cadenas sobre B = {0, 1, B} dadas por α1 = 01BB0B011BBB α2 = BB01BB0BB0BB α3 = 111B11BB0 se leen como β1 = 010011 β2 = 0100 β3 = 111110 Además, cadenas distintas sobre el alfabeto B pueden representar la misma cadena sobre el alfabeto A. Ejemplo 33 Las siguientes cadenas sobre B α1 = 01B011BBB α2 = B01BB0B1BB1 α3 = 0101B1BB α4 = BB0B1B0B1B1B representan la misma cadena sobre A dada por 01011 Por lo tanto, una misma variable candidata a formar parte de la solución del problema puede tener distintas codificaciones. Ası́ que, con el fin de unificar la codificación de las variables y para poder simplificar los procesos de cruce y mutación que estudiaremos más 72 tarde, reordenaremos de la siguiente forma las codificaciones: recorriendo de izquierda a derecha la codificación desplazamos los sı́mbolos B al final de la cadena. Con esta reordenación conseguimos construir un representante de todas las posibles codificaciones de una misma variable. Ejemplo 34 Sea la codificación dada por 0B10B00B1BB1B001 tras la reordenación obtenemos la cadena 0100011001BBBBBB que representa todas las posibles codificaciones de la variable 0100011001. Nota 20 La cadena formada sólo por sı́mbolos blancos representa la cadena vacia, Λ. Una vez codificadas las variables del problema, definimos un individuo como la concatenación de las cadenas αi para i = 1, . . . , m que se obtienen al codificar las m variables x1 , . . . , xm . Ejemplo 35 Supongamos que tenemos que codificar 4 variables de longitud menor o igual a 6 sobre el alfabeto binario A. Sean α1 = B01B10 α2 = BB1BB0 α3 = 11BB1B Lo primero que tenemos que hacer es reordenar los sı́mbolos, obteniendo α1 = 0110BB α2 = 10BBBB α3 = 111BBB Un individuo o solución candidata del problema viene dada por la cadena I = α1 α2 α3 = 0110BB | {z } 10BBBB | {z } 111BBB | {z } α1 α2 73 α3 Nota 21 No tiene sentido reordenar las cadenas que representan individuos de la población, pues perderı́amos la información de las variables que componen esas posibles soluciones del problema. Para finalizar esta sección, queremos destacar que con esta codificación de los individuos conseguimos que todos tengan la misma longitud, m · l. Además, todas las variables son codificadas como cadenas de longitud igual a l, aunque la longitud real de la variable que se representa es menor o igual que l, pues el sı́mbolo B no aporta nada al valor de las variables. Por lo tanto, buscamos soluciones en un espacio de búsqueda de tamaño à l !m X 2i = (2l+1 − 1)m i=0 5.3. Generación de la Población Inicial Trabajaremos con poblaciones de tamaño fijo, tp. Aunque el algoritmo que diseñaremos permitirá al usuario elegir el tamaño de la población con el que quiere resolver el problema, uno de nuestro primeros objetivos será determinar el tamaño de la población más adecuado para la resolución de los problemas l SW ES. Una vez fijado el tamaño tp, la población inicial será generada de forma aleatoria. Para la implementación de este proceso proponemos dos métodos. Método 1. Cada individuo de la población inicial se genera como sigue: para cada i = 1, . . . , m eligimos de forma aleatoria un número entre 0 y l, que denotaremos por lr (i). Después, construimos aleatoriamente una cadena βi de longitud lr (i) sobre el alfabeto A y una cadena β̄i formada por el sı́mbolo B de longitud igual a l − lr (i). La cadena αi se obtiene como la concatenación de βi y β̄i . αi = βi β̄i Finalmente, definimos el individuo I = α1 · · · αm Ejemplo 36 Generamos una población de tamaño tp = 3 para resolver un problema 4 SW ES con dos variables. (l = 4) 74 Empezamos definiendo el primer individuo de la población inicial, I1 . Para i = 1 elegimos al azar la longitud de la subcadena β1 , sea lr (1) = 2 ∈ {0, 1, . . . , 4}. Construimos aleatorimente β1 = 01. Definimos β̄1 = BB. Se obtiene la cadena α1 = β1 β̄1 = 01BB. Para i = 2 elegimos al azar la longitud de la subcadena β2 , sea lr (2) = 4 ∈ {0, 1, . . . , 4}. Construimos aleatoriamente β2 = 0110. Definimos β̄2 = Λ. Se obtiene la cadena α2 = β2 β̄2 = 0110Λ = 0110. Por lo tanto, se tiene que I1 = α1 α2 = 01BB0110 Definimos ahora el segundo individuo de la población inicial, I2 . Para i = 1 elegimos al azar la longitud de la subcadena β1 , sea lr (1) = 1 ∈ {0, 1, . . . , 4}. Construimos aleatoriamente β1 = 1. Definimos β̄1 = BBB. Se obtiene la cadena α1 = β1 β̄1 = 1BBB. Para i = 2 elegimos al azar la longitud de la subcadena β2 , sea lr (2) = 0 ∈ {0, 1, . . . , 4}. Construimos aleatoriamente β2 = Λ. Definimos β̄2 = BBBB. Se obtiene la cadena α2 = β2 β̄2 = ΛBBBB = BBBB. Por lo tanto, se tiene que I2 = α1 α2 = 1BBBBBBB Finalmente, generamos el último individuo, I3 . Para i = 1 elegimos al azar la longitud de la subcadena β1 , sea lr (1) = 3 ∈ {0, 1, . . . , 4}. Construimos aleatoriamente β1 = 101. Definimos β̄1 = B. Se obtiene la cadena α1 = β1 β̄1 = 101B. Para i = 2 elegimos al azar la longitud de la subcadena β2 , sea lr (2) = 3 ∈ {0, 1, . . . , 4}. Construimos aleatoriamente β2 = 111. Definimos β̄2 = B. Se obtiene la cadena α2 = β2 β̄2 = 111B. Por lo tanto, se tiene que I3 = α1 α2 = 101B111B De esta forma, hemos generado al azar una población inicial de tamaño tp = 3. Método 2. Cada individuo de la población inicial se genera como sigue: para cada i = 1, . . . , m construimos al azar una cadena αi de longitud l sobre el 75 alfabeto B y luego reordenamos dicha cadena para que todos los sı́mbolos blancos estén situados al final de la misma. Finalmente, definimos el individuo I = α1 · · · αm Ejemplo 37 Generamos una población de tamaño tp = 3 para resolver un problema 4 SW ES con dos variables. (l = 4) Empezamos definiendo el primer individuo de la población inicial, I1 . Para i = 1 construimos aleatorimente la cadena α1 = B1B0, después de la reordenación se tiene que α1 = 10BB. Para i = 2 construimos aleatoriamente la cadena α2 = BB1B, después de la reordenación se tiene que α2 = 1BBB. Por lo tanto, I1 = α1 α2 = 10BB1BBB Definimos ahora el segundo individuo de la población inicial, I2 . Para i = 1 construimos aleatorimente la cadena α1 = 1BB1, después de la reordenación se tiene que α1 = 11BB. Para i = 2 construimos aleatoriamente la cadena α2 = 0B01, después de la reordenación se tiene que α2 = 001B. Por lo tanto, I1 = α1 α2 = 11BB001B Finalmente, generamos el último individuo, I3 . Para i = 1 construimos aleatorimente la cadena α1 = 0000, después de la reordenación se tiene que α1 = 0000. Para i = 2 construimos aleatoriamente la cadena α2 = BBBB, después de la reordenación se tiene que α2 = BBBB. Por lo tanto, I1 = α1 α2 = 0000BBBB De esta forma, hemos generado al azar una población inicial de tamaño tp = 3. 76 5.4. Evaluación de los Individuos Este es un paso muy importante en el diseño del algoritmo genético, pues tenemos que encontrar una función fitness sencilla, es decir, con poco coste a la hora de evaluar los individuos, y que al mismo tiempo nos permita guiar el proceso de búsqueda de las soluciones de la forma más eficiente posible. Un individuo I = α1 · · · αm es solución del problema l SW ES si al sustituir las subcadenas αi por las variables xi , para i = 1, . . . , m, en el sistema de “word equations” se obtienen identidades. Ejemplo 38 Consideramos el problema 3 SW ES dado por el sistema 01x1 1 = x2 1x1 x1 01x2 = 10x2 1 Generamos una población de tamaño tp = 2 dada por I1 = 11B01B I2 = 1BB01B Veamos si alguno de los individuos de esta población es solución del problema. Empezamos sustituyendo el individuo I1 . Como, I1 = 11B01B resulta que α1 = 11B - x1 = 11 α2 = 01B - x2 = 01 - 01x1 1 = x2 1x1 - 01111 = 01111 x1 01x2 = 10x2 1 - 110101 6= 10011 Es decir, I1 no es solución. Ahora, comprobamos si I2 es solución. Como, I2 = 1BB01B - α1 = 1BB - x1 = 1 α2 = 01B - x2 = 01 77 resulta que 01x1 1 = x2 1x1 - 0111 = 0111 x1 01x2 = 10x2 1 - 10101 6= 10011 I2 tampoco es solución. Observamos que aunque ninguno de los dos individuos de la población son solución, ambos hacen que se verifique la primera de las ecuaciones del sistema. Sin embargo, parece estar más proximo a la solución el individuo I2 pues sólo dos sı́mbolos de la segunda ecuación del sistema no coinciden. Este ejemplo, nos sugiere definir el fitness asociado a un individuo como el número de sı́mbolos que no coinciden al sustituir dicho individuo en el sistema. De esta forma, nuestro objetivo será minimizar el valor de la función fitness, es decir, conseguir que nigún sı́mbolo sea distinto del correspondiente situado en el otro lado de la ecuación. Definición 44 Sea S = {L1 = R1 , . . . , Ln = Rn } un l SW ES. Se define el fitness asociado a un individuo I como el valor dado por n X (max{lk , rk } − ck ) f (I) = k=1 siendo lk = |Lk (I)|, rk = |Rk (I)| y ck el número de sı́mbolos que coinciden al sustituir el individuo I en la k ecuación del sistema, para cada k = 1, . . . , n. Nota 22 Al sustituir en cada ecuación Lk = Rk el individuo I, obtenemos dos cadenas Lk (I) y Rk (I) sobre el alfabeto A cuyas longitudes hemos denotado por lk y rk , respectivamente. El individuo I será solución del sistema si Lk (I) = Rk (I) para cada k = 1, . . . , n. Ejemplo 39 Volviendo al sistema del anterior ejemplo 01x1 1 = x2 1x1 x1 01x2 = 10x2 1 Tenı́amos una población dada por I1 = 11B01B I2 = 1BB01B 78 Veamos quien es el fitness asociado a cada uno de estos individuos: Al sustituir I1 en el sistema se obtiene L1 (I1 ) = 01111 con l1 = 5 R1 (I1 ) = 01111 con r1 = 5 L2 (I1 ) = 110101 con l2 = 6 R2 (I1 ) = 10011 con r2 = 5 Como L1 (I1 ) = R1 (I1 ), todos los sı́mbolos del lado izquierdo de la ecuación coinciden con los del lado derecho, por lo tanto, c1 = 5. Luego, f1 (I1 ) = max{l1 , r1 } − c1 = 5 − 5 = 0. En la segunda ecuación resulta que L2 (I1 ) 6= R2 (I1 ). Contamos los sı́mbolos que ocupando la misma posición en ambas cadenas coinciden. L2 (I1 ) = 1 1 01 01 R2 (I1 ) = 1 0 01 1 Se obtiene que c2 = 3. Luego, f2 (I1 ) = max{l2 , r2 } − c2 = max{6, 5} − 3 = 6 − 3 = 3. Por lo tanto, f (I1 ) = 2 X k=1 fk (I1 ) = 2 X (max{lk , rk } − ck ) = 0 + 3 = 3 k=1 Al sustituir I2 en el sistema se obtiene L1 (I2 ) = 0111 con l1 = 4 R1 (I2 ) = 0111 con r1 = 4 L2 (I2 ) = 10101 con l2 = 5 R2 (I2 ) = 10011 con r2 = 5 Como L1 (I2 ) = R1 (I2 ) todos los sı́mbolos del lado izquierdo de la ecuación coinciden con los del lado derecho, por lo tanto, c1 = 4. Luego, f1 (I2 ) = max{l1 , r1 } − c4 = 4 − 4 = 0. En la segunda ecuación resulta que L2 (I2 ) 6= R2 (I1 ). Contamos los sı́mbolos que ocupando la misma posición en ambas cadenas coinciden. L2 (I2 ) = 10 10 1 R2 (I2 ) = 10 01 1 79 Se obtiene que c2 = 3. Luego, f2 (I2 ) = max{l2 , r2 } − c2 = max{5, 5} − 3 = 5 − 3 = 2. Por lo tanto, f (I2 ) = 2 X fk (I2 ) = k=1 2 X (max{lk , rk } − ck ) = 0 + 2 = 2 k=1 Como el fitness asociado a I2 es menor que el asociado a I1 podemos concluir que es mejor candidata a solución la cadena I2 . Nota 23 Cuando sustituimos un individuo en una ecuación obtenemos dos cadenas sobre el alfabeto A, las correspondientes al lado derecho e izquierdo de la ecuación, que no tienen por que tener la misma longitud. Una condición necesaria para que ambas cadenas coincidan es que tengan la misma longitud, por eso a la hora de calcular el fitness no sólo contamos los sı́mbolos que ocupando las mismas posiciones en ambas cadenas no coinciden sino que también contamos los sı́mbolos que hacen que una cadena tenga mayor longitud, es decir, los sı́mbolos situados entre la posición min{lk , rk } + 1 y max{lk , rk }. 5.5. Los Operadores Genéticos Selección Usaremos un criterio de selección elitista que mantiene en la población al mejor individuo alcanzado hasta ese momento. Además, la selección de los individuos que darán lugar a una nueva población se hará de acuerdo al fitness asociado mediante el método de la ruleta. En este caso, pretendemos minizar la función fitness, por lo que los individuos con un fitness más pequeño son los que mayor probabilidad tiene de contribuir en la siguinte población. La probabilidad de contribuir en la siguiente población viene dada por tp X P(i) = f (j) − f (i) j=1 tp tp XX ( f (j) − f (l)) l=1 j=1 para cada individuo i = 1, . . . , tp. Ejemplo 40 Supongamos que tenemos una población de tamaño tp = 4 y hemos evaluado los individuos obteniendo la siguiente tabla 80 Individuo fitness I1 2 I2 12 I3 5 I4 7 Es evidente que el individuo con mayor probabilidad de contribuir en la siguiente generación es I1 . Luego, la probabilidad de contribuir en la siguiente población es inversamente proporcional al fitness. Construimos una ruleta de la siguiente forma: sea sum la suma de los fitness de la población, sum = 4 X f (Ii ) i=1 definimos para cada individuo un nuevo fitness dado por fe(Ii ) = sum − f (Ii ) Ahora, la probabilidad de que un individuo contribuya en la siguiente población es directamente proporcional al nuevo fitness. En este ejemplo, sum = 2 + 12 + 5 + 7 = 26 Individuo fitness f^ itness P fe(Ii )/ 4j=1 fe(Ij ) I1 2 24 24/78 = 0,31 I2 12 14 14/78 = 0,18 I3 5 21 21/78 = 0,27 I4 7 19 19/78 = 0,24 Cruce Para cada par de individuos 1 I1 = α11 · · · αm 2 I2 = α12 · · · αm se define el operador de cruce como un proceso que cruza cada subcadena αi1 = βi1 β̄i1 con la subcadena αi2 = βi2 β̄i2 , para i = 1, . . . , m, en dos etapas, obteniendose un 81 nuevo individuo. Recordar que cada subcadena αi sobre el alfabeto B = {0, 1, B} está formada por dos subcadenas βi y β̄i siendo βi una cadena de longitud menor o igual que l sobre el alfabeto A = {0, 1} y β̄i una cadena de sı́mbolos blancos. En la primera etapa, se aplica un cruce uniforme como sigue: 1. Se calcula la posición k = min{|βi1 |, |βi2 |} 2. Para cada j = 1, . . . , k elijo con probabilidad p el sı́mbolo que ocupa la posición j ésima de αi1 y con probabilidad 1 − p tomamos el de la subcadena αi2 , este sı́mbolo pasa a ocupar la posición j ésima de la subcadena αi en el nuevo individuo, es decir, estamos aplicando un cruce uniforme a las cadenas αi1 (1, k) y αi2 (1, k). En nuestro algoritmo tomaremos p = 0,5. Ejemplo 41 Vamos a aplicar la primera etapa del cruce a las subcadenas BB αi1 = 011101 | {z } |{z} αi2 βi1 β̄i1 = 1101 | {z } |{z } BBBB βi2 β̄i2 Calculamos la posición k = min{|βi1 |, |βi2 |} = min{6, 4} = 4 Por lo tanto, aplicamos un cruce uniforme a las cadena αi1 (1, 4) = 0111 αi2 (1, 4) = 1101 Obteniendo, por ejemplo, la cadena αi (1, 4) = 0101 En la segunda etapa, aplicamos un cruce en un punto como sigue: 1. Elegimos una posición q ∈ {k, . . . , l}, siendo l la longitud de las cadenas αi1 y αi2 , para i = 1, . . . , m. 82 2. Si una de las dos cadenas αi1 (k + 1, l) ó αi2 (k + 1, l) contiene algun sı́mbolo distinto de B, definimos αi (k + 1, q) con las q − k primeras posiciones de esa cadena y completamos la subcadena αi con l − q sı́mbolos blancos, es decir, αi (q + 1, l) = B l−q . En otro caso, se define αi (k + 1, l) = B l−k Nota 24 Si q = k entonces αi = (k + 1, l) = B l−k Ejemplo 42 En el ejemplo anterior aplicamos la primera etapa del cruce a las subcadenas αi1 = 011101BB αi2 = 1101BBBB Obtuvimos αi (1, 4) = 0101. Ahora aplicamos la segunda etapa del cruce para definir αi (5, 8). Para ello, tenemos que eligir una posición entre 4 y 8, sea q = 7. Como αi1 (5, 8) = 01BB tiene sı́mbolos distintos del blanco, definimos αi (k+1, q) = αi (5, 7) con los q − k = 3 primeros sı́mbolos de αi1 (5, 8) = 01BB, es decir, αi (5, 7) = 01B. Finalmente, completamos la cadena αi con l − q = 1 sı́mbolos blancos. Hemos construido la subcadena αi = 010101BB Repitiendo este proceso para cada par de subcadenas αi1 y αi2 conseguimos definir un nuevo individuo a partir de los individuos I1 e I2 . Mutación Cada individuo de la población será mutado con probabilidad p, fijada. La probabilidad de mutación será otro de los datos que el usuario fija para resolver los problemas. En nuestro caso, buscaremos la probabilidad de mutación más adecuada para resolver los problemas l SW ES. Si un individuo I = α1 · · · αm es seleccionado para la mutación, entonces se aplica un proceso de mutación a cada subcadena αi = βi β̄i de la siguiente forma: cada sı́mbolo de la subcadena αi cambia con probabilidad 1l , siendo l la longitud de la subcadena αi . 83 Recordar que la subcadena αi toma valores en el alfabeto B. Ası́ pues, tras el proceso de mutación es necesario reordenar de nuevo para situar los sı́mbolos blancos al final. Hemos definido todas los elementos necesarios para el diseño del algoritmo genético simple. En el apendice A se encuentra el código de este algoritmo escrito en el lenguaje C++. Veamos con un sencillo ejemplo su funcionamiento. Ejemplo 43 Consideramos el problema 3 SW ES dado por las ecuaciones x2 x1 = 10x2 x2 10x1 = 1x1 0x1 Codificación de los individuos Cada individuo se obtiene como la concatenación de dos cadenas de longitud l = 3 sobre el alfabeto B. Población inicial Trabajaremos con poblaciones de tamaño tp = 4. La población inicial será generada usando el método 2. Usaremos el fitness y los operadores genéticos que acabamos de definir. Empezamos a resolver el problema ejecutando el algoritmo genético simple ası́ diseñado. Generamos la población inicial: α11 = 0B1 - α11 = 01B - α21 - α12 = 011 - α22 α13 = 101 - α13 = 101 α23 = BB0 - α23 = 0BB α21 = B11 α12 = 011 α22 = B1B = 11B = 1BB 84 - I10 = 01B11B - I20 = 0111BB - I30 = 1010BB α14 = BB1 - α14 = 1BB α24 = 1B0 - α24 = 10B I40 = 1BB10B - Obtenemos la pobalción inicial P0 dada por los individuos: I10 = 01B11B I20 = 0111BB I30 = 1010BB I40 = 1BB10B Evaluamos la población inicial: x2 10x1 = 1x1 0x1 I10 111001 6= 101001 I10 1101 6= 1011 I20 110011 6= 10110011 I20 1011 6= 101 I30 010101 6= 11010101 I30 0101 6= 100 I40 10101 6= 1101 I40 101 6= 1010 - x2 x1 = 10x2 - x2 10x1 = 1x1 0x1 - x2 x1 = 10x2 - x2 10x1 = 1x1 0x1 - x2 x1 = 10x2 - x2 10x1 = 1x1 0x1 - x2 x1 = 10x2 - - f1 = 3 - f2 = 8 - f3 = 7 - f4 = 5 Se tiene la siguiente tabla asociada a la población inicial P0 Individuo P0 fitness f^ itness P (Ii ) I10 01B11B 3 20 0.29 I20 0111BB 8 15 0.22 I30 1010BB 7 16 0.23 I40 1BB10B 5 18 0.26 El mejor individuo de P0 , I10 , pasa directamente a la siguiente población, P1 . Individuo P1 fitness I11 01B11B 3 ··· ··· ··· 85 Tenemos que generar otros tres individuos mediante los operadores genéticos para completar la población P1 . Generación de I21 : Selección I10 = 01B11B −→ α11 = 01B α21 = 11B I30 = 1010BB −→ α13 = 101 α23 = 0BB Cruce Empezamos aplicando el cruce a las cadenas α11 = 01B y α13 = 101. De aplicar el cruce uniforme a las subcadenas 01 y 10 resulta la subcadena 00. De aplicar el cruce en un punto a las subcadenas B y 1 resulta la subcadena B. Por lo tanto, hemos obtenido α13 = 101 - α11 = 01B α1 = 00B Repetimos el proceso para las cadenas α21 = 11B y α23 = 0BB. De aplicar el cruce uniforme a las subcadenas 1 y 0 resulta la subcadena 0. De aplicar el cruce en un punto a las subcadenas 1B y BB resulta la subcadena 1B. Por lo tanto, hemos obtenido α23 = 0BB - α21 = 11B α2 = 01B Luego, I21 = 00B01B. Mutación Este individuo es sometido al proceso de mutación, resultando 00B −→ B0B −→ 0BB 01B −→ 01B −→ 01B - I21 = 0BB01B Generación de I31 : Selección I20 = 0111BB −→ α12 = 011 α22 = 1BB I10 = 10B11B −→ α11 = 01B α21 = 11B 86 Cruce Empezamos aplicando el cruce a las cadenas α12 = 011 y α11 = 01B. De aplicar el cruce uniforme a las subcadenas 01 y 01 resulta la subcadena 01. De aplicar el cruce en un punto a las subcadenas 1 y B resulta la subcadena 1. Por lo tanto, hemos obtenido α11 = 01B α12 = 011 - α1 = 011 Repetimos el proceso para las cadenas α22 = 1BB y α21 = 11B. De aplicar el cruce uniforme a las subcadenas 1 y 1 resulta la subcadena 1. De aplicar el cruce en un punto a las subcadenas BB y 1B resulta la subcadena BB. Por lo tanto, hemos obtenido α21 = 11B α22 = 1BB - α2 = 1BB Luego, I21 = 0111BB. Mutación Este individuo es sometido al proceso de mutación, resultando 011 −→ B01 −→ 01B 0BB −→ 0B1 −→ 01B - I21 = 01B01B Repitiendo este proceso generamos los otros individuos de la población. Tras la evaluación de los nuevos individuos se obtine la siguiente tabla para la población P1 , Individuo P0 fitness I11 01B11B 3 I21 01B01B 4 I31 1BB11B 5 I41 01B10B 0 Como uno de los individuos de la nueva población tiene fitness 0, el algoritmo ha finalizado. La solución propuesta es x1 = 01 x2 = 10 87 Capı́tulo 6 El Algoritmo Genero Problema Estamos en condiciones de empezar a experimentar con el algoritmo genético simple, pero nos encontramos con un inconveniente: no existen documentos donde aparezcan problemas del tipo de los que pretendemos resolver, ası́ que nos vemos obligados a construir los problemas l SW ES. Para facilitar este trabajo hemos decidido diseñar un algoritmo que construye un sistema de “word equations” sobre el alfabeto A = {0, 1} con un número de ecuaciones y de variables fijado. Este algoritmo también proporcionará una solución del problema, pero no tiene por que ser la única. La primera idea para el diseño de este algoritmo consistı́a en realizar los siguientes pasos: 1. Generar un sistema de identidades sobre el alfabeto A. 2. Generar las variables como cadenas de longitud menor o igual que lmax sobre el alfabeto A. 3. Para cada variable generada, recorrer el sistema buscando una subcadena que coincida con la variable. Si la encuentro, sustituyo con probabilidad p la subcadena por la letra que representa a la variable y lo sigo recorriendo en busca de otra coincidencia. Sin embargo, esta idea tan sencilla no nos garantiza que, al final, el sistema generado contenga todas las variables pues, por ejemplo, una variable puede ser generada como una cadena sobre A que no es subcadena en ninguna de las ecuaciones del sistema. Logicamente, fijado un número de ecuaciones, n, y un número de variables, m, nuestro interés es construir un sistema de n “word equations” sobre el alfabeto A que contenga 88 exactamente m variables, x1 , . . . , xm . Por lo tanto, nos vemos obligados a modificar esta primera idea y construimos un algoritmo con los siguientes pasos: 1. Generar un sistema de identidades sobre el alfabeto A = {0, 1}. 2. Para cada variable xi , a) elegimos una ecuación del sistema, b) elegimos el lado derecho o izquierdo de la ecuación, c) definimos la variable xi como una subcadena de la parte elegida (derecha o izquierda) de la ecuación, esta subcadena será de longitud menor o igual que lmax sobre A, d) sustituimos la subcadena de la ecuación por la letra que representa a la variable generada. De esta forma se consigue que cada variable generada aparezca al menos una vez en el sistema. 3. Una vez que se han generado todas las variables, recorremos el sistema en busca de coincidencias para cada variable xi , i = 1, . . . , m. Si se encuentra una subcadena en el sistema que coincida con la variable xi , se cambia con probabilidad p la subcadena por la letra que representa a la variable. Con este paso pretendemos aumentar el número de veces que aparece una variable en el sistema. Nota 25 A medida que se van generando las variables, el sistema de identidades sobre el alfabeto A se convierte en un sistema de ecuaciones sobre el conjunto A ∪ {x1 , . . . , xm }. El paso crı́tico de este algoritmo se encuentra al definir las variables del sistema. Intentaremos explicar un poco más este paso. El algoritmo que diseñamos necesita, además del número de ecuaciones y el número de variables, dos cotas le y lmax . La primera de ellas, le , indica la longitud máxima de las cadenas que generamos sobre A para construir las identidades (paso 1). La segunda cota, indica la longitud máxima de las variables. Por lo tanto, una vez que se ha elegido el lado de la ecuación donde se vá a definir la variable xi , se selecciona al azar una posición q de la misma y se cuentan los sı́mbolos (consecutivos) del alfabeto A que hay a la derecha de q. Si denotamos por lq al número de sı́mbolos del alfabeto A que hay a la derecha de q, entonces la variable xi se define como un subcadena 89 de longitud lr ∈ {0, min{lmax , lq }} que coincide con el trozo de subcadena de la ecuación que vá de q a q + lr . Notar que si lr = 0, entonces la variable xi es la palabra vacı́a. Nota 26 En general, el problema(n m lmax ) denotará un sistema de n “word equations” con m variables, de modo que cada variable (al menos en una solución del sistema) tiene longitud menor o igual que lmax . En la última versión de este algoritmo, hemos añadido un proceso para controlar el tipo de ecuaciones que constituyen el sistema. Con este proceso se pretenden evitar las ecuaciones donde el lado derecho coincide con el izquierdo. Por ejemplo, la ecuación 0x1 10 = 0x1 10 puede ser eliminada del sistema por tratarse de una igualdad. De modo que una vez generado el sistema lo sometemos a los siguientes pasos: 1. Recorremos el sistema en busca de ecuaciones con la misma cadena en ambos lados. 2. Para ecuación de este tipo, a) Recorremos la ecuación para cada variable xi buscando una coincidencia. Si hay coincidencia, se sustituye con probabilidad p la subcadena de la ecuación por la letra que representa dicha variable. b) Si depués de recorrer la ecuación para cada variable ambos lados siguen siendo iguales, sustituimos dicha ecuación por una nueva identidad sobre el alfabeto A y volvemos al paso a). De esta forma conseguimos que el sistema contenga exactamente n ecuaciones (con ambos lados distintos). En el apéndice C se encuentran algunos de los problemas que hemos generado con este algoritmo. Además, en el apéndice B se encuentra el código del algoritmo Genero Problema escrito en el lenguaje C++. 90 Capı́tulo 7 Primeros Resultados Experimentales 7.1. Introducción Cada experimento consistirá en la resolución, cincuenta veces, de un problema dado y con unos parámetros fijos. Estos parámetros serán: 1. El tamaño de la población. (tp) 2. La longitud máxima de las variables (l). Ası́, cada individuo estará constituido por subcadenas de longitud l sobre el alfabeto B. 3. El número máximo de evaluaciones a realizar en una ejecución. (NMax. Eval.) 4. La probabilidad de mutación. (Mutación) 5. La selección del método para la generación de la población inicial. (Método) Nota 27 Con el fin de comparar los resultados obtenidos hemos acotado el número de evaluaciones que puede realizar el algoritmo para resolver cada problema. Llamaremos Media Eval. al número medio de evaluaciones que fueron necesarias para encontrar solución en las pruebas exitosas de las cincuenta realizadas. Llamaremos Éxito al número de veces que se encuentra solución para un problema. Se expresará en tanto por cien. 91 7.2. Tamaño de Población Nuestro primer objetivo es determinar el tamaño de población más adecuado para la resolución de los diferentes problemas. No sabemos nada acerca de cuál es la mejor probabilidad de mutación, ası́ que hemos decidido tomar 0,6. Acotamos el proceso con 150000 evaluaciones y tomamos l = 5. Aprovecharemos también este experimento para establecer, si es posible, una diferencia entre el método 1 y el método 2 usados para generar la población inicial. Resolvemos el Problema15-12-4 Tamaño del espacio de búsqueda: aprox. 260 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 60 % 150000 22 % 106887 2 2 5 60 % 150000 34 % 107556 3 1 5 60 % 150000 12 % 108542 3 2 5 60 % 150000 6% 109582 4 1 5 60 % 150000 2% 88213 4 2 5 60 % 150000 0% 0 6 1 5 60 % 150000 0% 0 6 2 5 60 % 150000 0% 0 8 1 5 60 % 150000 0% 0 8 2 5 60 % 150000 0% 0 10 1 5 60 % 150000 0% 0 10 2 5 60 % 150000 0% 0 Los mejores resultados obtenidos corresponden a tamaño de población igual a dos. Además, no se observa una diferencia entre el método 1 y el método 2 que nos permita decir que uno es mejor que el otro, al menos en este problema concreto. Nota 28 Siempre encuentra la misma solución y la mayorı́a de las variables que la forman son la cadena vacı́a, Λ. 92 Resolvemos el Problema25-8-5 Tamaño del espacio de búsqueda: aprox. 248 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 60 % 150000 88 % 27667.5 2 2 5 60 % 150000 94 % 22222.3 3 1 5 60 % 150000 86 % 23132.5 3 2 5 60 % 150000 74 % 29488.7 4 1 5 60 % 150000 70 % 26295.2 4 2 5 60 % 150000 70 % 23354.7 6 1 5 60 % 150000 62 % 48727 6 2 5 60 % 150000 64 % 48273.5 8 1 5 60 % 150000 62 % 65029.6 8 2 5 60 % 150000 56 % 75479.5 10 1 5 60 % 150000 40 % 87881.9 10 2 5 60 % 150000 32 % 95393.1 Otra vez los mejores resultados se obtienen para tamaño de población igual a dos. Tampoco parece que sea determinante la elección del método que genera la población inicial. Los mejores resultados corresponden a Usando el método 1 Usando el método 2 tp Exito Media Eval. tp Exito Media Eval. 2 88 % 27667.5 2 94 % 22222.3 3 86 % 23132.5 3 74 % 29488.7 4 70 % 26295.2 4 70 % 23354.7 Nota 29 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 93 Resolvemos el Problema10-3-3 Tamaño del espacio de búsqueda: aprox. 212 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 60 % 150000 100 % 278.08 2 2 5 60 % 150000 100 % 223.78 3 1 5 60 % 150000 100 % 345.96 3 2 5 60 % 150000 100 % 282.84 4 1 5 60 % 150000 100 % 332.08 4 2 5 60 % 150000 100 % 407.8 6 1 5 60 % 150000 100 % 649.7 6 2 5 60 % 150000 100 % 615.6 8 1 5 60 % 150000 100 % 845.9 8 2 5 60 % 150000 100 % 757.98 10 1 5 60 % 150000 100 % 1033.48 10 2 5 60 % 150000 100 % 1261.54 Problema aparentemente sencillo que nos permite comprobar que mayor tamaño de población no mejora los resultados pero sı́ produce un aumento en el número de evaluaciones, por ejemplo, pasamos de 1261 evaluaciones para una población tp = 10 a tan sólo 223 evaluaciones para una población tp = 2. Parece que el método 2 reduce el número de evaluaciones pero no es una diferencia lo suficientemente grande como para decantarnos por este método en lugar del método 1. Los mejores resultados corresponden a Usando el método 1 Usando el método 2 tp Exito Media Eval. tp Exito Media Eval. 2 100 % 278.08 2 100 % 223.78 4 100 % 332.08 3 100 % 282.84 3 100 % 345.96 4 100 % 407.8 Nota 30 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 94 Resolvemos el Problema10-5-2 Tamaño del espacio de búsqueda: aprox. 215 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 60 % 150000 100 % 1118.56 2 2 5 60 % 150000 100 % 1323.76 3 1 5 60 % 150000 100 % 2261.4 3 2 5 60 % 150000 100 % 2412.48 4 1 5 60 % 150000 100 % 4590.82 4 2 5 60 % 150000 100 % 4229.5 6 1 5 60 % 150000 100 % 13627.5 6 2 5 60 % 150000 100 % 16416.7 8 1 5 60 % 150000 100 % 27001.4 8 2 5 60 % 150000 100 % 31674.5 10 1 5 60 % 150000 100 % 36888.2 10 2 5 60 % 150000 100 % 47870.9 Como en el problema anterior, el algoritmo ha encontrado solución en todas las ocasiones pero el número de evaluaciones ha ido creciendo a medida que hemos aumentado el tamaño de la población. Y este aumento es considerable, pues hemos pasado de 2261.4 evaluaciones con una población tp = 3 a 36888.2 evaluaciones con una población tp = 10. Respecto a la elección del método para la generación de la población inicial nos encontramos en la misma situación del problema10-3-3. Los mejores resultados corresponden a Usando el método 1 Usando el método 2 tp Exito Media Eval. tp Exito Media Eval. 2 100 % 1118.56 2 100 % 1323.76 3 100 % 2261.4 3 100 % 2412.48 4 100 % 4590.82 4 100 % 4229.5 Nota 31 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 95 Resolvemos el Problema12-6-4 Tamaño del espacio de búsqueda: aprox. 230 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 60 % 150000 100 % 706.18 2 2 5 60 % 150000 100 % 702.72 3 1 5 60 % 150000 100 % 1089.4 3 2 5 60 % 150000 100 % 1080.08 4 1 5 60 % 150000 100 % 2279.8 4 2 5 60 % 150000 100 % 2313.4 6 1 5 60 % 150000 100 % 5529.3 6 2 5 60 % 150000 100 % 6020.5 8 1 5 60 % 150000 100 % 9966.48 8 2 5 60 % 150000 100 % 10437 10 1 5 60 % 150000 100 % 15989.1 10 2 5 60 % 150000 100 % 17786.8 Como en los dos casos anteriores el problema12-6-4 ha resultado ser sencillo y el número de evaluaciones aumenta a medida que crece el tamaño de la población. Los mejores resultados corresponden a Usando el método 1 Usando el método 2 tp Exito Media Eval. tp Exito Media Eval. 2 100 % 706.18 2 100 % 702.72 3 100 % 1089.4 3 100 % 1080.08 4 100 % 2279.8 4 100 % 2313.4 Nota 32 Encuentra distintas soluciones. 96 Resolvemos el Problema5-3-2 Tamaño del espacio de búsqueda: aprox. 29 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 60 % 150000 100 % 338.6 2 2 5 60 % 150000 100 % 611.4 3 1 5 60 % 150000 100 % 622.68 3 2 5 60 % 150000 100 % 890.12 4 1 5 60 % 150000 100 % 858.7 4 2 5 60 % 150000 100 % 1664.44 6 1 5 60 % 150000 100 % 1487 6 2 5 60 % 150000 100 % 2822.7 8 1 5 60 % 150000 100 % 3280.5 8 2 5 60 % 150000 100 % 5467.16 10 1 5 60 % 150000 100 % 4207.6 10 2 5 60 % 150000 100 % 6347.44 Otra vez el número de evaluaciones aumenta a medida que crece el tamaño de la población. En esta ocasión si existe una diferencia clara entre los dos métodos propuestos para generar la población inicial, llegando a ser de casi la mitad de evaluaciones con el método 1. Los mejores resultados corresponden a Usando el método 1 Usando el método 2 tp Exito Media Eval. tp Exito Media Eval. 2 100 % 338.6 2 100 % 611.4 3 100 % 622.68 3 100 % 890.12 4 100 % 858.7 4 100 % 1664.44 Nota 33 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 97 Conclusión 1 Después de este primer contacto con el algoritmo genético resulta evidente que el tamaño de población más adecuado es tp = 2 pues a medida que crece el número de individuos de la población disminuye la probabilidad de éxito y aumenta el número de evaluaciones. Sin embargo, no estamos en condiciones de afirmar que uno de los dos métodos propuestos para la generación de la población inicial sea mejor que el otro. Por eso, seguiremos realizando los experimentos con los dos métodos. Resumen de los mejores resultados obtenidos: Problema Esp. Búsq. tp Exito Media Eval. Método 15-12-4 260 2 34 % 107556 2 25-8-5 248 2 94 % 22222.3 2 10-3-3 212 2 100 % 223.78 2 10-5-2 215 2 100 % 1118.56 1 12-6-4 230 2 100 % 702.72 2 5-3-2 29 2 100 % 338.6 1 Nota 34 En los sucesivos experimentos tomaremos siempre tamaño de población tp = 2 y acotaremos los procesos por 1.5 millones de evaluaciones. 98 7.3. Probabilidad de mutación Nuestro segundo objetivo es determinar los valores de la probabilidad de mutación más adecuados para la resolución de los problemas l SW ES. Para ello, fijamos todos los parámetros excepto la mutación que irá variando desde el 20 % al 100 %. Resolvemos el Problema10-15-5 Tamaño del espacio de búsqueda: aprox. 290 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 0% 0 2 2 5 20 % 1.5e+06 0% 0 2 1 5 40 % 1.5e+06 2% 1.06558e+06 2 2 5 40 % 1.5e+06 0% 0 2 1 5 70 % 1.5e+06 0% 0 2 2 5 70 % 1.5e+06 2% 1.12725e+06 2 1 5 80 % 1.5e+06 2% 1.37572e+06 2 2 5 80 % 1.5e+06 0% 0 2 1 5 90 % 1.5e+06 2% 801145 2 2 5 90 % 1.5e+06 2% 1.0302e+06 2 1 5 100 % 1.5e+06 4% 1.04644e+06 2 2 5 100 % 1.5e+06 2% 253817 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 100 % 4% 1.04644e+06 100 % 2% 253817 90 % 2% 801145 90 % 2% 1.0302e+06 Nota 35 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 99 Resolvemos el Problema10-5-1 Tamaño del espacio de búsqueda: aprox. 210 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 100 % 22751.7 2 2 5 20 % 1.5e+06 100 % 41875.7 2 1 5 40 % 1.5e+06 100 % 11364.6 2 2 5 40 % 1.5e+06 100 % 17492.3 2 1 5 70 % 1.5e+06 100 % 7952.02 2 2 5 70 % 1.5e+06 100 % 7800.06 2 1 5 80 % 1.5e+06 100 % 7923.1 2 2 5 80 % 1.5e+06 100 % 8119.02 2 1 5 90 % 1.5e+06 100 % 8101.9 2 2 5 90 % 1.5e+06 100 % 6422.6 2 1 5 100 % 1.5e+06 100 % 5594.96 2 2 5 100 % 1.5e+06 100 % 5600.88 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 100 % 100 % 5594.96 100 % 100 % 5600 80 % 100 % 7923.1 90 % 100 % 6422.6 Aunque el algoritmo siempre encuentra solución, el número de evaluaciones disminuye considerablemente para valores de la probabilidad de mutación altos. Nota 36 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 100 Resolvemos el Problema10-8-5 Tamaño del espacio de búsqueda: aprox. 248 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 98 % 381874 2 2 5 20 % 1.5e+06 94 % 304587 2 1 5 40 % 1.5e+06 98 % 203427 2 2 5 40 % 1.5e+06 100 % 180120 2 1 5 70 % 1.5e+06 100 % 129486 2 2 5 70 % 1.5e+06 100 % 179024 2 1 5 80 % 1.5e+06 100 % 134950 2 2 5 80 % 1.5e+06 100 % 114512 2 1 5 90 % 1.5e+06 98 % 90263.3 2 2 5 90 % 1.5e+06 100 % 93544.5 2 1 5 100 % 1.5e+06 100 % 86991.9 2 2 5 100 % 1.5e+06 100 % 73362.4 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 100 % 100 % 86991.9 100 % 100 % 73362.4 70 % 100 % 129486 90 % 100 % 93544.5 En esta ocasión, los valores de la probabilidad de mutación hacen que el algoritmo no siempre encuentre solución y además aumentan el número de evaluaciones. Nota 37 Encuentra distintas soluciones. Una de las variables del problema puede tomar distintos valores, el resto de las variables siempre toman el mismo valor. 101 Resolvemos el Problema25-8-3 Tamaño del espacio de búsqueda: aprox. 232 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 100 % 117905 2 2 5 20 % 1.5e+06 100 % 121339 2 1 5 40 % 1.5e+06 100 % 47327.7 2 2 5 40 % 1.5e+06 100 % 60389.5 2 1 5 70 % 1.5e+06 100 % 41581.5 2 2 5 70 % 1.5e+06 100 % 32645.8 2 1 5 80 % 1.5e+06 100 % 31538.3 2 2 5 80 % 1.5e+06 100 % 44212.9 2 1 5 90 % 1.5e+06 100 % 31687.5 2 2 5 90 % 1.5e+06 100 % 28286.8 2 1 5 100 % 1.5e+06 100 % 26726.1 2 2 5 100 % 1.5e+06 100 % 32858.1 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 100 % 100 % 26726.1 90 % 100 % 28286.8 80 % 100 % 31538.3 70 % 100 % 32645.8 Aunque siempre encuentra solución, el número de evaluaciones que fueron necesarias para encontrar solución disminuye para valores de la probabilidad de mutación entre el 70 % y el 100 %. Nota 38 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 102 Resolvemos el Problema25-8-5 Tamaño del espacio de búsqueda: aprox. 248 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 100 % 140364 2 2 5 20 % 1.5e+06 100 % 98455.6 2 1 5 40 % 1.5e+06 100 % 57316.9 2 2 5 40 % 1.5e+06 100 % 79764.9 2 1 5 70 % 1.5e+06 100 % 26307.5 2 2 5 70 % 1.5e+06 100 % 36819.7 2 1 5 80 % 1.5e+06 100 % 32292.8 2 2 5 80 % 1.5e+06 100 % 30747.8 2 1 5 90 % 1.5e+06 100 % 20602.3 2 2 5 90 % 1.5e+06 100 % 34846.2 2 1 5 100 % 1.5e+06 100 % 35748.3 2 2 5 100 % 1.5e+06 100 % 29859.6 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 90 % 100 % 20602.3 100 % 100 % 29859.6 70 % 100 % 26307.5 80 % 100 % 30747.8 De nuevo, el éxito es del 100 % para todos los experimentos realizados con este problema, pero el número de evaluaciones mejora para valores de la probabilidad de mutación entre el 70 % y el 100 %. Nota 39 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. 103 Resolvemos el Problema5-15-3 Tamaño del espacio de búsqueda: aprox. 260 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 4% 881354 2 2 5 20 % 1.5e+06 0% 0 2 1 5 40 % 1.5e+06 4% 750816 2 2 5 40 % 1.5e+06 0% 0 2 1 5 70 % 1.5e+06 2% 91868 2 2 5 70 % 1.5e+06 10 % 273665 2 1 5 80 % 1.5e+06 8% 681722 2 2 5 80 % 1.5e+06 0% 0 2 1 5 90 % 1.5e+06 2% 374402 2 2 5 90 % 1.5e+06 10 % 511213 2 1 5 100 % 1.5e+06 10 % 743411 2 2 5 100 % 1.5e+06 4% 1.00708e+06 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 100 % 10 % 743411 70 % 10 % 273665 80 % 8% 681722 90 % 10 % 511213 El algoritmo genético simple no siempre encuentra solución. Notar que el tamaño del espacio de búsqueda es de aprox. 260 . A pesar de ello, los valores de probabilidad de mutación entre el 70 % y el 100 % proporcionan los mejores resultados. Nota 40 Encuentra distintas soluciones. La gran mayorı́a de las variables, aproximádamente 10 de las 15, pueden tomar distintos valores. 104 Resolvemos el Problema15-12-4 Tamaño del espacio de búsqueda: aprox. 260 tp Método l Mutación NMax Eval. Exito Media Eval. 2 1 5 20 % 1.5e+06 76 % 638268 2 2 5 20 % 1.5e+06 80 % 658522 2 1 5 40 % 1.5e+06 84 % 386301 2 2 5 40 % 1.5e+06 86 % 496717 2 1 5 70 % 1.5e+06 96 % 324814 2 2 5 70 % 1.5e+06 98 % 313743 2 1 5 80 % 1.5e+06 92 % 277741 2 2 5 80 % 1.5e+06 98 % 288551 2 1 5 90 % 1.5e+06 100 % 263719 2 2 5 90 % 1.5e+06 100 % 204263 2 1 5 100 % 1.5e+06 98 % 295557 2 2 5 100 % 1.5e+06 96 % 255491 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Mutación Exito Media Eval. Mutación Exito Media Eval. 90 % 100 % 263719 90 % 100 % 204263 100 % 98 % 295557 80 % 98 % 288551 Podemos observar que los valores de probabilidad de mutación entre el 80 % y el 100 % no sólo mejoran el éxito sino que también reducen el número de evaluaciones necesarias para encontrar solución. Nota 41 Siempre encuentra la misma solución y coincide con la propuesta por Genero Problema. La mayorı́a de las variables son la cadena vacia, Λ. 105 Conclusión 2 Resumen de los mejores resultados obtenidos: Usando el método 1 Problema Esp. Búsq. Mutación Exito Media Eval. 10-15-5 290 100 % 4% 1.04644e+06 10-5-1 210 100 % 100 % 5594.96 10-8-5 248 100 % 100 % 86991.9 25-8-3 232 100 % 100 % 26726.1 25-8-5 248 90 % 100 % 20602.3 5-15-3 260 100 % 10 % 743411 15-12-4 260 90 % 100 % 263719 Usando el método 2 Problema Esp. Búsq. Mutación Exito Media Eval. 10-15-5 290 100 % 2% 253817 10-5-1 210 100 % 100 % 5600 10-8-5 248 100 % 100 % 73362.4 25-8-3 232 90 % 100 % 28286.8 25-8-5 248 100 % 100 % 29859.6 5-15-3 260 70 % 10 % 273665 15-12-4 260 90 % 100 % 204263 Se de duce que los mejores resultados se obtienen con una probabilidad de mutación comprendida entre el 70 % y el 100 %. Aunque puede parecer que la probabilidad de mutación es muy alta, hemos obtenido valores similares a los propuestos en la resolución de los problemas SAT , que en definitiva es nuestra única referencia. 106 Capı́tulo 8 Algoritmos Evolutivos para Resolver l SW ES A partir del algoritmo genético simple, definido en el capı́tulo 5, vamos a construir dos algoritmos evolutivos. La codificación de los individuos, la generación de la población inicial, el fitness y los operadores genéticos se definen de la misma forma. Además, seguiremos acotando la resolución de los problemas por 1,5 millones de evaluaciones y buscaremos soluciones cuyas variables sean de longitud menor o igual que cinco. 8.1. A.E. Sólo Mutación Como consecuencia de los primeros resultados experimentales, el alto valor que toma la probabilidad de mutación y teniendo en cuenta los algoritmos evolutivos que fueron propuestos para resolver SAT (ver capı́tulo 2), resulta interesante modificar nuestro algoritmo genético para obtener un algoritmo evolutivo que trabaje con una población de tamaño tp = 1. Este algoritmo evolutivo usará sólo el operador genético de mutación. Si los resultados que proporcionan este algoritmo mejoran los del algoritmo genético simple, seguiremos nuestro estudio con este nuevo algoritmo o bien intentaremos modificar el operador de cruce para mejorar el algoritmo genético simple. El esquema de funcionamiento de este algoritmo es el siguiente 107 Seudocódigo I0 ← generar población inicial I0 ← evaluar (I0 ) para k = 1 hasta NIter hacer I1 ← copiar (I0 ) I1 ← mutar (I1 ) E1 ← evaluar (I1 ) I0 ← mejor individuo (I0 , I1 ) fin para En el apéndice D se encuentra este algoritmo escrito en C++. En las siguientes tablas comparamos los resultados obtenidos al resolver un mismo problema l SW ES con el algoritmo genético simple (AGS) y con este nuevo algoritmo evolutivo (AE). Usando el método 1 para generar la población inicial se obtuvo Problema Esp. Búsq. Exito(AGS) Exito(AE) Eval.(AGS) Eval.(AE) 10-5-1 210 100 % 100 % 5594.96 5599.28 10-8-5 248 100 % 78 % 86991.9 366416 25-8-3 232 100 % 88 % 26726.1 293262 25-8-5 248 100 % 66 % 35748.3 39866 5-15-3 260 10 % 6% 743411 461058 15-12-4 260 98 % 100 % 263719 203817 Usando el método 2 para generar la población inicial se obtuvo Problema Esp. Búsq. Exito(AGS) Exito(AE) Eval.(AGS) Eval.(AE) 10-5-1 210 100 % 100 % 5600 5155.18 10-8-5 248 100 % 66 % 73362.4 405752 25-8-3 232 100 % 88 % 32858.1 385027 25-8-5 248 100 % 60 % 29859.6 14243.1 5-15-3 260 4% 0% 1.00708e+06 0 15-12-4 260 96 % 100 % 255491 250908 108 Resulta evidente que el AE no mejora los resultados obtenidos con el AGS. Por lo tanto, el operador de cruce que hemos definido tiene influencia en la resolución de los sistemas. 8.2. A.E. Búsqueda Local Añadimos al algoritmo genético simple un proceso de búsqueda local que se aplicará a cada individuo de las sucesivas poblaciones generadas, con el fin de mejorar (localmente) el fitness de dichos individuos. Para la implementación de este proceso definimos dos métodos: Búsqueda Local 1 Dado un individuo I = α1 · · · αm , cualquiera. Se realizan dos etapas: 1. Cada subcadena αi , para i = 1, . . . , m, se somete a un proceso de busqueda local clásico que afecta unicamente a las cadena βi , para i = 1, . . . , m. (Recordar que αi = βi β̄i siendo βi ∈ A∗ y β̄i ∈ {B}∗ ) Este proceso consiste en recorrer de izquierda a derecha los sı́mbolos de las cadenas βi , para i = 1, . . . , m, cambiando cada sı́mbolo, es decir, se pasa de cero a uno o viceversa. Cada vez que se cambia un sı́mbolo se evalua el nuevo individuo y nos quedamos con el mejor, es decir, si al cambiar un sı́mbolo de una de las cadenas βi el fitness del nuevo individuo mejora o iguala al del individuo anterior, entonces nos quedamos con el nuevo y pasamos a cambiar el siguiente sı́mbolo situado más a la derecha (que pertenezca a una cadena βi ). Pero si el fitness del individuo no mejora al cambiar un sı́mbolo, entonces se recupera el sı́mbolo original y se pasa al siguiente. De esta forma, se recorren todos los sı́mbolos de βi , para i = 1, . . . , m y se cambiando aquellos sı́mbolos que mejoren el fitness del individuo. Si al final de este proceso el fitness del individuo ha mejorado, entonces volvemos a aplicar la búsqueda local clásica a las nuevas cadenas βi . 2. Cuando la búsqueda local clásica no permite mejorar el fitness del individuo I = α1 · · · , αm , se aplica una búsqueda local que permite modificar en una unidad la longitud de las cadenas βi , para i = 1, . . . , m. 109 Notar que hasta ahora lo único que hacı́amos era cambiar los sı́mbolos, pasando de cero a uno o viceversa, pero la longitud de las cadenas βi , para i = 1, . . . , m no cambiaba. Para modificar la longitud de la cadena βi , para i = 1, . . . , m en una unidad, procedemos como sigue: a) Disminuimos en una unidad la longitud de βi convirtiendo el último sı́mbolo de esta cadena en un sı́mbolo blanco, de modo que la longitud de la cadena β̄i aumenta una unidad. Ejemplo 44 Sea βi β̄i = 0010BBB con |βi | = 4 y |β̄i | = 3. Al disminuir la longitud de βi en una unidad se obtiene βi β̄i = 001BBBB siendo ahora |βi | = 3 y |β̄i | = 4. b) Aumentamos en una unidad la longitud de la cadena βi convirtiendo el primer sı́mbolo blanco de la cadena β̄i en un sı́mbolo del alfabeto A, de modo que la longitud de β̄i disminuye en una unidad. Como A = {0, 1}, tenemos dos posibilidades para aumentar la longitud de βi : 1) convertir en un cero el primer sı́mbolo blanco de β̄i , 2) convertir en uno el primer sı́mbolo blanco de β̄i . Ejemplo 45 Sea βi β̄i = 0010BBB con |βi | = 4 y |β̄i | = 3. Al aumentar la longitud de βi en una unidad se obtiene dos cadenas 00100BB 00101BB siendo ahora |βi | = 5 y |β̄i | = 2. Luego, para cadena αi obtenemos tres posibilidades al modificar en una unidad la longitud de βi , si alguna de estas nuevas cadenas mejora el fitness del individuo, entonces αi es sustituida por la nueva cadena. 110 Cuando terminamos de modificar las longitudes de todas las cadenas βi en busca de un individuo mejor, comprobamos si el fitness ha mejorado, en ese caso se vuelve a el paso 1. Si no hay mejora, se termina el proceso de búsqueda local 1 aplicado al individuo I. Búsqueda Local 2 Dado un individuo I = α1 · · · αm , cualquiera. A cada subcadena αi , se le aplica el proceso de búsqueda local clásica (que afecta unicamente a la cadena βi ) y después se le aplica el proceso de búsqueda local que permite modificar en una unidad la longitud de βi , tomando aquella que mejore el fitness del individuo. Una vez que hemos aplicado estos dos procesos a todas las subcadenas αi del individuo I, si el fitness del individuo ha mejorado volvemos a repetir el proceso. En otro caso, la búsqueda local 2 aplicada al individuo I finaliza. Nota 42 Observese que aunque ambos procedimientos de búsqueda local puedan parecer iguales, hay una importante diferencia: mientras que en el proceso de búsqueda local 1 realizamos sucesivas veces (hasta que no se produzca mejora) lo que hemos denominado búsqueda local clásica antes de tratar de modificar las longitudes, en el proceso de búsqueda local 2 realizamos en cada iteración una vez la búsqueda local clásica seguida de una posible modificación en una unidad de las longitudes. 8.3. Resultados experimentales Hasta ahora hemos resuelto los problemas con ayuda de un algoritmo genético simple. En esta sección vamos a estudiar los resultados obtenidos al añadir búsqueda local. Se intentará establecer, si fuese posible, cuál de los dos métodos implementados para el proceso de búsqueda local es mejor. También resultará interesante determinar si la mutación sigue siendo importante cuando se usa búsqueda local. Para ello iremos tomando distintos valores de probabilidad de mutación. 111 Finalmente, compararemos los resultados obtenidos al aplicar un algoritmo genético simple para resolver los problemas l SW ES y los resultados obtenidos al utilizar un A.E. con búsqueda local. Para poder comparar los resultados seguimos manteniendo tamaño de pobalción tp = 2, longitud máxima de las variables que forman los individuos igual a 5 y acotamos la ejecución del algoritmo por 1.5 millones de evaluaciones. Resolvemos el Problema10-3-3 Tamaño del espacio de búsqueda: aprox. 218 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 85.34 100 % 50.9 2 5 70 % 100 % 79.94 100 % 50.26 2 5 90 % 100 % 77.1 100 % 50.26 2 5 100 % 100 % 77.1 100 % 50.26 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 90.06 100 % 44.06 2 5 70 % 100 % 73.72 100 % 46.9 2 5 90 % 100 % 75.96 100 % 50.32 2 5 100 % 100 % 75.96 100 % 50.32 Parece que la probabilidad de mutación no influye sobre los resultados. Sin embargo, si queda reflejado la superioridad de la búsqueda local 2 frente a la 1. Nota 43 Siempre encuentra la misma solución. 112 Resolvemos el Problema10-8-5 Tamaño del espacio de búsqueda: aprox. 248 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 26705.3 100 % 19301.1 2 5 70 % 100 % 15371.6 100 % 10368.7 2 5 90 % 100 % 15947.3 100 % 10164.8 2 5 100 % 100 % 16017.8 100 % 8220.76 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 27036.5 100 % 21981.9 2 5 70 % 100 % 16879.7 100 % 12433.7 2 5 90 % 100 % 15947.3 100 % 9702.92 2 5 100 % 100 % 15585 100 % 9629.52 Se observa que la búsqueda local 2 reduce el número de evaluaciones. Además, a medida que aumenta la probabilidad de mutación disminuye el número de evaluaciones. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 100 % 100 % 8220.76 2 100 % 100 % 9629.52 2 90 % 100 % 10164.8 2 90 % 100 % 9702.92 Nota 44 Admite distintas soluciones. Una de las variables del problema puede tomar distintos valores, el resto de las variables siempre toman el mismo valor. 113 Resolvemos el Problema10-8-3 Tamaño del espacio de búsqueda: aprox. 248 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 35040.2 100 % 22728.9 2 5 70 % 100 % 19519.9 100 % 16844.7 2 5 90 % 100 % 23727.3 100 % 20473.6 2 5 100 % 100 % 22740 100 % 16484.9 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 49985.8 100 % 28200.4 2 5 70 % 100 % 24295.2 100 % 22562 2 5 90 % 100 % 27747.4 100 % 18935.5 2 5 100 % 100 % 31129.4 100 % 16596.1 De nuevo hay que destacar la diferencia que existe entre los métodos de búsqueda local implementados. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mutación Exito Media Eval. 2 100 % 100 % 16484.9 2 100 % 100 % 16596.1 2 70 % 100 % 16844.7 2 90 % 100 % 18935.5 Nota 45 Siempre encuentra la misma solución. 114 Resolvemos el Problema10-15-5 Tamaño del espacio de búsqueda: aprox. 290 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 484850 100 % 593998 2 5 70 % 100 % 474252 100 % 288598 2 5 90 % 100 % 182997 100 % 104509 2 5 100 % 100 % 457354 100 % 340361 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 831604 100 % 646195 2 5 70 % 100 % 589418 100 % 338841 2 5 90 % 100 % 472892 100 % 362079 2 5 100 % 100 % 499089 100 % 340070 En este caso podemos observar que no siempre la mejor probabilidad de mutación es el 100 %, pues aunque siempre se encuentra solución el número de evaluaciones necesarias varı́a. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 90 % 100 % 104509 2 70 % 100 % 338841 1 90 % 100 % 182997 2 100 % 100 % 340070 Nota 46 Siempre encuentra la misma solución. 115 Resolvemos el Problema10-15-3 Tamaño del espacio de búsqueda: aprox. 290 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 44 % 611542 76 % 338663 2 5 70 % 50 % 652274 94 % 327947 2 5 90 % 68 % 642407 98 % 362663 2 5 100 % 56 % 583024 92 % 264417 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 38 % 646377 72 % 614781 2 5 70 % 48 % 616960 94 % 424924 2 5 90 % 34 % 643584 86 % 401649 2 5 100 % 64 % 573071 92 % 413873 Como en todos los problemas anteriores la búsqueda local 2 es más efectiva. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 90 % 98 % 362663 2 70 % 94 % 424924 2 70 % 94 % 327947 2 100 % 92 % 413873 Nota 47 Admite distintas soluciones. Existen dos variables del problema que puede tomar distintos valores, el resto de las variables siempre toman el mismo valor. 116 Resolvemos el Problema15-12-4 Tamaño del espacio de búsqueda: aprox. 272 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 1676.5 100 % 1187.36 2 5 70 % 100 % 1377.96 100 % 1154.76 2 5 90 % 100 % 1484.46 100 % 860.82 2 5 100 % 100 % 1254.8 100 % 1058.64 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 2289.98 100 % 1729.18 2 5 70 % 100 % 1988.92 100 % 1456.82 2 5 90 % 100 % 2110.4 100 % 1395.68 2 5 100 % 100 % 2032.8 100 % 1431.26 La búsqueda local resulta ser un proceso tan potente que permite en muchas ocasiones encontrar solución aplicandolo unicamente a la población inicial, es decir, no se llega a usar el algoritmo genético simple. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 90 % 100 % 860.82 2 90 % 100 % 1395.68 2 100 % 100 % 1058.64 2 100 % 100 % 1431.26 Nota 48 Siempre encuentra la misma solución. La mayorı́a de las variables son la cadena vacia, Λ. 117 Resolvemos el Problema15-7-5 Tamaño del espacio de búsqueda: aprox. 242 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 225.48 100 % 226.56 2 5 70 % 100 % 225.48 100 % 225.26 2 5 90 % 100 % 290.3 100 % 216.64 2 5 100 % 100 % 290.3 100 % 225.64 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 354.9 100 % 374.8 2 5 70 % 100 % 402.16 100 % 268.84 2 5 90 % 100 % 347.18 100 % 322.48 2 5 100 % 100 % 341.42 100 % 255.94 En la mayorı́a de los casos la búsqueda local aplicada a la población inicial es suficiente para encontrar la solución del problema, sólo en algunos casos necesita unas pocas iteraciones del algoritmo genético para encontrarla. Por eso no existe diferencia entre los resultados obtenidos para las distintas probabilidades de mutación. Nota 49 Admite distintas soluciones. Existen dos variables del problema que puede tomar distintos valores, el resto de las variables siempre toman el mismo valor. 118 Resolvemos el Problema25-8-3 Tamaño del espacio de búsqueda: aprox. 248 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 2938.72 100 % 2336.18 2 5 70 % 100 % 1748.66 100 % 1525.2 2 5 90 % 100 % 1280.28 100 % 1405.06 2 5 100 % 100 % 1839.48 100 % 1054.16 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 3599.8 100 % 3011.56 2 5 70 % 100 % 2282.54 100 % 1665.56 2 5 90 % 100 % 2487.56 100 % 1630.5 2 5 100 % 100 % 2375.56 100 % 2222.46 Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 100 % 100 % 1054.16 2 90 % 100 % 1630.5 1 90 % 100 % 1280.28 2 70 % 100 % 1665.56 Nota 50 Siempre encuentra la misma solución. 119 Resolvemos el Problema25-8-5 Tamaño del espacio de búsqueda: aprox. 248 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 4674.82 100 % 2522.74 2 5 70 % 100 % 2782.76 100 % 1517.26 2 5 90 % 100 % 3174.1 100 % 1407.38 2 5 100 % 100 % 2613.64 100 % 1189.84 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 100 % 3502.56 100 % 1710.58 2 5 70 % 100 % 2591.5 100 % 1244 2 5 90 % 100 % 2788.2 100 % 1145.1 2 5 100 % 100 % 2468.54 100 % 1165.28 Destacar de nuevo la diferencia entre el número de evaluaciones necesarias para encontrar solución usando los distintos métodos de búsqueda local. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 100 % 100 % 1189.84 2 90 % 100 % 1145.1 2 90 % 90 % 1407.38 2 100 % 100 % 1165.28 Nota 51 Siempre encuentra la misma solución. 120 Resolvemos el Problema5-15-3 Tamaño del espacio de búsqueda: aprox. 290 Usando el método 1 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 94 % 535684 88 % 311136 2 5 70 % 96 % 299938 96 % 171128 2 5 90 % 94 % 246925 100 % 192842 2 5 100 % 94 % 317776 98 % 246269 Usando el método 2 para generar la población inicial se obtuvo Búsq. Local 1 Búsq. Local 2 tp l Mutación Exito Media Eval. Exito Media Eval. 2 5 20 % 62 % 541543 96 % 391371 2 5 70 % 94 % 415857 94 % 284990 2 5 90 % 88 % 341641 94 % 222077 2 5 100 % 92 % 316503 96 % 218227 En este problema, la búsqueda local 2 no sólo mejora el número de evaluaciones sino que aumenta la probabilidad de éxito, es decir, de encontrar solución. Los mejores resultados se obtienen para Usando el método 1 Usando el método 2 Búsq. L. Mut. Exito Media Eval. Búsq. L. Mut. Exito Media Eval. 2 90 % 100 % 192842 2 100 % 96 % 218227 2 100 % 98 % 246269 2 20 % 96 % 391371 Nota 52 Admite distintas soluciones. 121 Conclusión 3 Resumen de los mejores resultados obtenidos: Usando el método 1 Problema Búsq. L. Mutación Exito Media Eval. 10-8-5 2 100 % 100 % 8220.76 10-8-3 2 100 % 100 % 16484.9 10-3-3 2 — 100 % 50.26 10-15-5 2 90 % 100 % 104509 10-15-3 2 90 % 98 % 362663 15-12-4 2 90 % 100 % 860.82 15-7-5 2 — 100 % 216.64 25-8-3 2 100 % 100 % 1054.16 25-8-5 2 100 % 100 % 1189.84 5-15-3 2 90 % 100 % 192842 Usando el método 2 Problema Búsq. L. Mutación Exito Media Eval. 10-8-5 2 100 % 100 % 9629.52 10-8-3 2 100 % 100 % 16596.1 10-3-3 2 — 100 % 44.06 10-15-5 2 70 % 100 % 338841 10-15-3 2 70 % 94 % 424924 15-12-4 2 90 % 100 % 1395.68 15-7-5 2 — 100 % 255.94 25-8-3 2 90 % 100 % 1630.5 25-8-5 2 90 % 100 % 1145.1 5-15-3 2 100 % 96 % 218227 Estamos en condiciones de afirmar que la búsqueda local 2 es más efectiva que la 1 y que la probabilidad de mutación sigue siendo una etapa del proceso importante en la resolución de los problemas. 122 Hemos podido obsevar que la búsqueda local resulta decisiva en la primera etapa, cuando se genera la población inicial. Antes de empezar a trabajar con el algoritmo genético, la población inicial es sometida a un proceso de búsqueda local y, como consecuencia, esta población experimenta una mejora considerable. Pero, una vez iniciado el algoritmo genético, el efecto que tiene la búsqueda local sobre las nuevas poblaciones generadas no es significativo. Por ese motivo, creemos que es importante mantener valores de probabilidad de mutación entre 70 % y el 100 %. Para concluir esta sección comparamos algunos de los resultados obtenidos al aplicar el algoritmo genético simple (AGS) y el algoritmo genético con búsqueda local (AE). Como ya sabemos que la búsqueda local 2 es mejor que la 1, tomamos como referencia los resultados obtenidos con busqueda local 2 para compararlos con los resultados obtenidos sin búsqueda local. Se tienen las siguientes tablas Usando el método 1 Problema Mutación Éxito(AGS) Éxito(AE) Media Eval.(AGS) Media Eval.(AE) 10-15-5 70 % 0% 100 % 0 288598 (290 ) 90 % 2% 100 % 801145 104509 100 % 4% 100 % 1.04644e+06 340361 10-8-5 70 % 100 % 100 % 129486 10368.7 (248 ) 90 % 98 % 100 % 90263.3 10164.8 100 % 100 % 100 % 86991.9 8220.76 15-12-4 70 % 96 % 100 % 324814 1154.76 (272 ) 90 % 100 % 100 % 263719 860.82 100 % 98 % 100 % 295557 1058.64 25-8-5 70 % 100 % 100 % 26307.5 1517.26 (248 ) 90 % 100 % 100 % 20602.3 1407.38 100 % 98 % 100 % 35748.3 1189.84 5-15-3 70 % 2% 96 % 91868 171128 (290 ) 90 % 2% 100 % 374402 192842 100 % 10 % 98 % 743411 246269 123 Usando el método 2 Problema Mutación Éxito(AGS) Éxito(AE) Media Eval.(AGS) Media Eval.(AE) 10-15-5 70 % 2% 100 % 1.12728e+06 338841 (290 ) 90 % 2% 100 % 1.0302e+06 362079 100 % 2% 100 % 253817 340070 10-8-5 70 % 100 % 100 % 179024 12433.7 (248 ) 90 % 100 % 100 % 93544.5 9702.92 100 % 100 % 100 % 73362.4 9629.52 15-12-4 70 % 98 % 100 % 313743 1456.82 (272 ) 90 % 100 % 100 % 204263 1395.68 100 % 96 % 100 % 255491 1431.26 25-8-5 70 % 100 % 100 % 36819.7 1244 (248 ) 90 % 100 % 100 % 34846.2 1145.1 100 % 100 % 100 % 29859.6 1165.28 5-15-3 70 % 10 % 94 % 273665 284990 (290 ) 90 % 10 % 94 % 511213 222077 100 % 4% 96 % 1.00708e+06 218227 Conclusión 4 La búsqueda local mejora la probabilidad de exito y reduce considerablemente el número de evaluaciones necesarias para encontrar una solución del problema. 124 Capı́tulo 9 Experimento final En esta sección vamos a presentar los resultados obtenidos en la resolución de un grupo de problemas clasificados como de gran dificultad por tener un espacio de búsqueda grande. Como ya hemos comprobado el mejor funcionamiento de la búsqueda local 2, en toda esta sección usaremos esta búsqueda con tamaño de población dos. Resolvemos el Problema15-25-5 Tamaño del espacio de búsqueda: aprox. 2150 Usando el método 1 para generar la población inicial se obtuvo Búsq. L. tp l Mutación Exito Media Eval. 2 2 5 70 % 92 % 414500 2 2 5 80 % 98 % 384799 2 2 5 90 % 96 % 359897 2 2 5 100 % 86 % 417172 Usando el método 2 para generar la población inicial se obtuvo Búsq. L. tp l Mutación Exito Media Eval. 2 2 5 70 % 98 % 475580 2 2 5 80 % 96 % 424660 2 2 5 90 % 90 % 409549 2 2 5 100 % 90 % 421117 125 Mientras que con el método 1 los mejores resultados se obtienes con probabilidad de mutación 80 % y 90 %, con el método 2 a medida que disminuye la probabilidad de mutación mejoran los resultados, esto nos lleva a pensar en intentar resolver el problema con menor probabilidad de mutación. Aunque según la experiencia no esperamos que haya mejora, pues hemos establecido la probabilidad de mutación más adecuada entre el 70 % y el 100 %. Nota 53 Admite distintas soluciones. Existen dos variables del problema que puede tomar distintos valores, el resto de las variables siempre toman el mismo valor. Resolvemos el Problema25-23-4 Tamaño del espacio de búsqueda: aprox. 2138 Usando el método 1 para generar la población inicial se obtuvo Búsq. L. tp l Mutación Exito Media Eval. 2 2 5 70 % 68 % 375905 2 2 5 80 % 78 % 549016 2 2 5 90 % 78 % 593530 2 2 5 100 % 76 % 601812 Usando el método 2 para generar la población inicial se obtuvo Búsq. L. tp l Mutación Exito Media Eval. 2 2 5 70 % 74 % 530816 2 2 5 80 % 72 % 690505 2 2 5 90 % 70 % 663918 2 2 5 100 % 70 % 519235 Se repite el comportamiento observado en el problema anterior, con el método 1 el mejor éxito se tiene con probabilidad de mutación igual a 80 %, seguido del 90 %, mientras que con el método 2 va mejorando el resultado a medida que disminuye la probabilidad de mutación. Nota 54 Siempre encuentra la misma solución. 126 Para finalizar, en la siguiente tabla se comparan los resultados obtenidos al resolver algunos problemas en distintos espacios de búsqueda con el algoritmo genético simple (AGS) y con el A.E. Búsqueda Local (AE). Usamos el método 1 para generar la población inicial y mutación del 90 % Problema l Esp. Búsq. Éxito(AE) Éxito(AGS) Eval.(AE) Eval.(AGS) p10-8-3 3 232 100 % 98 % 3593.14 243568 p25-8-3 3 232 100 % 100 % 771.66 86942 p10-8-3 4 240 100 % 72 % 7094 464914 p25-8-3 5 248 100 % 74 % 1405.06 393360 p25-8-3 6 256 100 % 42 % 2502.06 644780 p5-15-3 3 260 100 % 50 % 19332.8 290949 p10-15-3 3 260 100 % 62 % 9788.18 458878 p15-12-4 4 260 100 % 100 % 644.94 193523 p10-8-3 7 264 100 % 4% 221302 60725 p25-8-3 8 272 100 % 16 % 4946.56 389959 p10-8-3 10 288 76 % 0% 577926 – p5-15-3 5 290 100 % 6% 192842 366681 p10-15-3 5 290 98 % 10 % 362663 712000 p10-15-5 5 290 100 % 2% 104509 801145 p25-23-4 4 2115 96 % 0% 493575 – p25-23-4 5 2138 78 % 0% 593530 – p15-25-5 5 2150 96 % 0% 359897 – p5-15-3 10 2165 4% 0% 464710 – p25-8-3 20 2168 100 % 0% 31189.3 – Conclusión 5 El algoritmo evolutivo que proponemos para resolver los problemas l SW ES usa los operadores genéticos definidos en el capı́tulo 5 y aplica búqueda local 2 a cada una de las poblaciones generadas. 127 Apéndice A El Algoritmo Genético Simple // //////////////////////////////////////////////////////////// // // / // / / // Un Algoritmo Genético para Resolver el problema l_SWES // / / // / // // //////////////////////////////////////////////////////////// // #include<fstream.h> #include<stdlib.h> #include<vector.h> #include<stdio.h> #include<math.h> #include<string.h> using namespace std; fstream fsol("solucion.dat",ios::out); void Pedir_Datos(string &problema, int &TamPob, int &lMaxVsol, int &NMaxIter, int &ProMut, int &Metodo); void Lee_Prob(const string &problema, int &n_ec, int &m_var, vector<int> &l_ec, vector<vector<int> > &Sist); void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Sist); 128 void Genera_PobIni(vector<vector<vector<int> > > &PobIni, vector<vector<int> > &lReVsol, vector<vector<vector<int> > > &AuxPob, vector<vector<int> > &AuxlRsol, const int &TamPob, const int &m_var, const int &lMaxVsol, const int &Metodo); void reordeno(const int &l, vector<int> &v, int &lReVsol); void Evalua(const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, const vector<int> &lReVsol, const vector<vector<int> > &PobIni, vector<int> &AuxIzq, vector<int> &AuxDer, int &fitness, int &haysol); void sustituyo( const vector<int> &Sist, int &lAux, vector<int> &Aux, const vector<int> &lVsol, const vector<vector<int> > &PobIni); void Mejor_Sol(int &MejorFit, vector<int> &fitness, const int &TamPob, const int &m_var, vector<vector<int> > &AuxPob, const int &lMaxVsol, vector<vector<vector<int> > > &PobIni, vector<int> &AuxlRsol, vector<vector<int> > &lReVsol); void Ruleta(int &Sumfit, const int &TamPob, vector<int> &fitness); void eligo_padre(int &TamPob, int &Padre, vector<int> &fitness); void Cruce(const int &m_var, const int &lMaxVsol, 129 vector<vector<int> > &AuxPob, vector<int> &lReVsolIzq, vector<int> &lReVsolDer, vector<int> &AuxlRsol, vector<vector<int> > &PobIniIzq, vector<vector<int> > &PobIniDer); void Mutacion(const int &m_var, const int &lMaxVsol, int &muto, vector<vector<int> > &AuxPob, vector<int> &AuxlRsol); int main(void) { string problema; int TamPob, lMaxVsol, NMaxIter, ProMut, Metodo; int n_ec, m_var; int haysol, muto; int MejorFit, Sumfit, PadreIzq, PadreDer; int i, j, k, iter; vector<int> l_ec; vector<vector<int> > Sist; vector<vector<vector<int> > > PobIni; vector<vector<int> > lReVsol; vector<vector<vector<int> > > AuxPob; //vector auxiliar vector<vector<int> > AuxlRsol; //vector auxiliar vector<int> fitness; vector<int> AuxIzq; //Sólo se utiliza en la función evalua vector<int> AuxDer; //Sólo se utiliaza en la función evalua randomize(); Pedir_Datos(problema, TamPob, lMaxVsol, NMaxIter, ProMut, Metodo); Lee_Prob(problema, n_ec, m_var, l_ec, Sist); 130 Simplifica(n_ec, l_ec, Sist); Genera_PobIni(PobIni, lReVsol, AuxPob, AuxlRsol, TamPob, m_var, lMaxVsol, Metodo); fitness.resize(TamPob); haysol= 0; for(k= 0; k<TamPob; k++) Evalua(n_ec, l_ec, Sist, lReVsol[k], PobIni[k], AuxIzq, AuxDer, fitness[k], haysol); fsol<<"La poblacion inicial generada es: "<<endl; for(k= 0; k<TamPob; k++){ for(i= 0; i< m_var; i++){ for(j=0;j<lMaxVsol; j++) fsol<<PobIni[k][i][j]; fsol<<" "; } //fin fsol<<endl; fsol<<"Con fitness "<<fitness[k]<<endl; } //fin for(k) for(iter= 0; iter<NMaxIter && haysol == 0; iter++) { Mejor_Sol(MejorFit, fitness, TamPob, m_var, AuxPob[0], lMaxVsol, PobIni, AuxlRsol[0], lReVsol); Ruleta(Sumfit, TamPob, fitness); for(k= 1; k< TamPob; k++) { // * Selección * PadreIzq = rand() % Sumfit; eligo_padre(TamPob, PadreIzq, fitness); PadreDer = rand() % Sumfit; eligo_padre(TamPob, PadreDer, fitness); 131 // * Cruce * Cruce(m_var, lMaxVsol, AuxPob[k], lReVsol[PadreIzq], lReVsol[PadreDer], AuxlRsol[k], PobIni[PadreIzq], PobIni[PadreDer]); // * Mutación * if(ProMut!=0) { muto = rand() %100; if(muto< ProMut) // * Muto la solusión k_esima * Mutacion(m_var, lMaxVsol, muto, AuxPob[k], AuxlRsol[k]); }//fin if(ProMut!=0) }//fin for(k) for(k= 0; k< TamPob; k++) for(i= 0; i< m_var; i++) { lReVsol[k][i]= AuxlRsol[k][i]; for(j= 0;j< lMaxVsol; j++) PobIni[k][i][j]= AuxPob[k][i][j]; }//fin for(i) fitness[0]= MejorFit; for(k= 1; k<TamPob; k++) { Evalua(n_ec, l_ec, Sist, lReVsol[k],PobIni[k],AuxIzq, AuxDer, fitness[k], haysol); }//fin for(k) fsol<<"La poblacion generada en la ultima iteracion realizada es:"<<endl; 132 for(k= 0; k< TamPob; k++) { for(i= 0; i< m_var; i++) { for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[k][i][j]; fsol<<" ";}//fin fsol<<endl; fsol<<"Con fitness "<<fitness[k]<<endl; }//fin for(k) }//fin for(iter) if(haysol == 1) { fsol<<"El programa ha encontrado solucion con una poblacion de "<<endl; fsol<<"tama~ no "<<TamPob<<" y longitud máxima de las variables "<<endl; fsol<<"igual a "<<lMaxVsol<<" ."<<endl; fsol<<"La probabilidad de mutacion utiliza fue "<<ProMut<<" ."<<endl; fsol<<"La solucion propuesta tras "<<iter<<" iteraciones es: "<<endl; k=0; while(fitness[k] !=0) k= k + 1; for(i= 0; i< m_var; i++) { fsol<<"x"<<i<<" = "; if(lReVsol[k][i] == 0) fsol<<"la palabra vacia."<<endl; else { for(j= 0;j< lReVsol[k][i]; j++) fsol<<PobIni[k][i][j]<<" "; fsol<<endl; }//fin else }//fin for(i) }//fin if(haysol==1) if(haysol == 0) { fsol<<"El programa NO ha encontrado solucion."<<endl; fsol<<"Los datos propuestos fueron: "<<endl; fsol<<"Tama~ no de poblacion igual a "<<TamPob<<" ."<<endl; 133 fsol<<"Longitud máxima de las variables igual a "<<lMaxVsol<<" ."<<endl; fsol<<"La probabilidad de mutacion utiliza fue "<<ProMut<<" ."<<endl; }//fin if(haysol==0) fsol<<"La poblacion generada en la ultima iteracion realizada es: "<<endl; for(k= 0; k< TamPob; k++) { for(i= 0; i< m_var; i++) { for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[k][i][j]; fsol<<" "; }//fin for(i) fsol<<endl; fsol<<"Con fitness "<<fitness[k]<<endl; }//fin for(k) }//fin programa void Pedir_Datos(string &problema, int &TamPob, int &lMaxVsol, int &NMaxIter, int &ProMut, int &Metodo) { cout<<"Indique el nombre del fichero que contiene el problema: "<<endl; cin>> problema; do{ cout<<"Escriba el tamanio (>0) de la poblacion inicial:"<<endl; cin>> TamPob; }while(TamPob<=0); do{ cout<<"Escriba la longitud maxima(>0) de las candidatas a solucion:"<<endl; cin>> lMaxVsol; }while(lMaxVsol<=0); 134 do{ cout<<"Numero maximo de iteraciones que desea realizar : "<<endl; cin>>NMaxIter; }while(NMaxIter<0); do{ cout<<"Cual es la probabilidad de mutacion que desea utilizar?"<<endl; cout<<"Indique esta probabilidad en porcentaje de 100"<<endl; cin>> ProMut; cout<<"Recuerde que se trata de un valor entero entre cero y 100."<<endl; cout<<endl<<endl; }while(ProMut>100 || ProMut<0); cout<<"Eliga el metodo que desea utilizar para generar la poblacion"<<endl; cout<<"inicial."<<endl<<endl; do{ cout<<"Pulse 1 si quiere fijar la longitud real de la variable y "<<endl; cout<<"luego generar la variable con simbolos del alfabeto {0,1}."<<endl; cout<<endl; cout<<"Pulse 2 si quiere generar la variable en {0,1,2} y luego "<<endl; cout<<"calcular la longitud real de la variable."<<endl<<endl; cout<<"Pulse 3 si desea recibir mas informacion sobre los metodos"<<endl; cout<<"usados en la generacion de la poblacion inicial."<<endl<<endl; cin>> Metodo; if(Metodo == 3) { cout<<"El metodo-1 elige la longitud real de la variable que va a"<<endl; cout<<"generar entre cero (que corresponde a la palabra vacia) y "<<endl; cout<<"la longitud maxima propuesta. Una vez fijada la longitud, "<<endl; cout<<"genera la variable como una cadena de ceros y unos. "<<endl<<endl; cout<<"El metodo-2 genera la variable como una cadena de simbolos"<<endl; cout<<"pertenecientes al conjunto {0,1,2} de longitud la longitud"<<endl; cout<<"maxima propuesta. Despues reordena esta cadena, colocando"<<endl; cout<<"todos los doses al final de la misma (pues representan el "<<endl; 135 cout<<"simbolo vacio). Finalmente, calcula el numero de ceros y "<<endl; cout<<"unos que aparecen en la cadena y que corresponde a la "<<endl; cout<<"longitud real de la variable generada."<<endl<<endl; cout<<"Que metodo desea utilizar?"<<endl; cin>> Metodo; }//fin if }while(Metodo<1 || Metodo>2); }//fin Pedir_Datos void Lee_Prob(const string &problema, int &n_ec, int &m_var, vector<int> &l_ec, vector<vector<int> > &Sist) { ifstream fprob(problema.c_str(),ios::in); fprob>>n_ec; fprob>>m_var; l_ec.resize(2*n_ec); Sist.resize(2*n_ec); for(int i= 0; i< 2*n_ec; i++){ fprob>>l_ec[i]; Sist[i].resize(l_ec[i]); for(int j= 0; j< l_ec[i]; j++) fprob>>Sist[i][j]; }//fin for(i) }//fin Lee_Prob void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Sist) { int coincidencia, columnas, minimo; for(int i= 0; i< 2*n_ec - 1; i+= 2) { coincidencia= 0; columnas= 0; minimo= min (l_ec[i],l_ec[i+1]); while(Sist[i][columnas]== Sist[i+1][columnas] && columnas< minimo) { coincidencia= coincidencia +1; columnas= columnas +1; 136 }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i]-coincidencia; for(int j= 0; j< l_ec[i]; j++) Sist[i][j]= Sist[i][j+coincidencia]; Sist[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; for(int j= 0; j< l_ec[i+1]; j++) Sist[i+1][j]= Sist[i+1][j+coincidencia]; Sist[i+1].resize(l_ec[i+1]); }//fin if coincidencia= 0; columnas= 1; minimo= min (l_ec[i],l_ec[i+1]); while(columnas<= minimo && Sist[i][l_ec[i]-columnas] == Sist[i+1][l_ec[i+1]-columnas]) { coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i] -coincidencia; Sist[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; Sist[i+1].resize(l_ec[i+1]); }//fin if }//fin for(i) }//fin Simplifica void Genera_PobIni(vector<vector<vector<int> > > &PobIni, vector<vector<int> > &lReVsol, vector<vector<vector<int> > > &AuxPob, vector<vector<int> > &AuxlRsol, const int &TamPob, const int &m_var, const int &lMaxVsol, const int &Metodo) 137 { int longitud; PobIni.resize(TamPob); lReVsol.resize(TamPob); AuxPob.resize(TamPob); //vector auxiliar AuxlRsol.resize(TamPob); //vector auxiliar for(int k= 0; k< TamPob; k++) { PobIni[k].resize(m_var); lReVsol[k].resize(m_var); AuxPob[k].resize(m_var); //vector auxiliar AuxlRsol[k].resize(m_var); //vector auxiliar for(int i= 0; i< m_var; i++) { PobIni[k][i].resize(lMaxVsol); if(Metodo == 1) { longitud= lMaxVsol +1; lReVsol[k][i]= rand() % longitud; for(int j= 0; j< lReVsol[k][i]; j++) PobIni[k][i][j]= rand() % 2; for(int j= lReVsol[k][i]; j< lMaxVsol; j++) PobIni[k][i][j]= 2; }//fin if(Metodo==1) if(Metodo == 2) { lReVsol[k][i]= lMaxVsol; for(int j= 0; j< lMaxVsol; j++) PobIni[k][i][j] = rand() % 3; reordeno(lMaxVsol, PobIni[k][i],lReVsol[k][i]); }//fin if(Metodo==2) }//fin for(i) }//fin for(k) }//fin Genera_PobIni 138 void reordeno(const int &l, vector<int> &v,int &lReVsol) { // Reordeno la variable y calculo la longitud real de la misma int columna; for(int j= 0; j< l-1; j++) if(v[j] == 2) { columna= j+1; while(v[columna] == 2 && columna< l-1) columna = columna +1; if(v[columna]!= 2) { v[j]= v[columna]; v[columna]= 2; }//fin if }//if(v[j] == 2) for(int j= 0; j< l; j++) if(v[j] == 2) lReVsol= lReVsol -1; }//fin reordeno void Evalua(const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, const vector<int> &lReVsol, const vector<vector<int> > &PobIni, vector<int> &AuxIzq, vector<int> &AuxDer, int &fitness, int &haysol) { int lAuxIzq, lAuxDer; int coincidencia, minimo; coincidencia = 0; for(int i= 0; i<n_ec; i++) { lAuxIzq= l_ec[2*i]; lAuxDer= l_ec[2*i+1]; AuxIzq.resize(lAuxIzq); 139 AuxDer.resize(lAuxDer); sustituyo(Sist[2*i], lAuxIzq, AuxIzq, lReVsol, PobIni); sustituyo(Sist[2*i+1], lAuxDer, AuxDer, lReVsol, PobIni); minimo = min(lAuxIzq,lAuxDer); for(int j= 0;j< minimo; j++) if(AuxIzq[j] != AuxDer[j]) coincidencia= coincidencia+1; coincidencia= coincidencia + abs(lAuxIzq-lAuxDer); }//fin for(i) fitness= coincidencia; if(fitness == 0) haysol= 1; }//fin Evaluacion void sustituyo(const vector<int> &Sist, int &lAux, vector<int> &Aux, const vector<int> &lVsol, const vector<vector<int> > &PobIni) { int auxiliar, variable; int j, l; auxiliar = 0; for(j= 0;j< lAux; j++) if(Sist[j+auxiliar]==0 || Sist[j+auxiliar]==1) Aux[j] = Sist[j+auxiliar]; else { variable = Sist[j+auxiliar] - 3; if(lVsol[variable]==0) { lAux=lAux-1; Aux.resize(lAux); auxiliar=auxiliar+1; j=j-1; } else 140 { lAux=lAux+lVsol[variable]-1; Aux.resize(lAux); for(l= 0; l< lVsol[variable]; l++) Aux[j+l] = PobIni[variable][l]; auxiliar = auxiliar - lVsol[variable] + 1; j=j+lVsol[variable]-1; } }//fin else }//fin sustituyo void Mejor_Sol(int &MejorFit, vector<int> &fitness, const int &TamPob, const int &m_var, vector<vector<int> > &AuxPob, const int &lMaxVsol, vector<vector<vector<int> > > &PobIni, vector<int> &AuxlRsol, vector<vector<int> > &lReVsol) { int minimo, variable; variable = 0; MejorFit = fitness[0]; for(int k= 1; k< TamPob; k++) { if(fitness[k]< MejorFit) { MejorFit= fitness[k]; variable= k; }//fin if(fitness[k]< MejorFit) if(MejorFit == fitness[k]) { minimo= rand() % 2; if(minimo == 0) { MejorFit= fitness[k]; variable= k; 141 }//fin if(minimo == 0) }//fin if(MejorFit == fitness[k]) }//fin for(k) for(int i= 0; i< m_var; i++) { AuxPob[i].resize(lMaxVsol); AuxlRsol[i]= lReVsol[variable][i]; for(int j= 0; j< lMaxVsol; j++) AuxPob[i][j]= PobIni[variable][i][j]; }//fin for(i) }//fin Mejor_Sol void Ruleta(int &Sumfit, const int &TamPob, vector<int> &fitness) { Sumfit = 0; for(int k= 0; k< TamPob; k++) Sumfit= Sumfit + fitness[k]; for(int k= 0; k< TamPob; k++) fitness[k]= Sumfit - fitness[k]; Sumfit = 0; for(int k= 0; k< TamPob; k++) Sumfit= Sumfit + fitness[k]; }//fin Ruleta void eligo_padre(int &TamPob, int &Padre, vector<int> &fitness) { int limInf, limSup, coinciden; coinciden= 0; limInf = 0; limSup = fitness[0]; for(int i= 0; i<TamPob && coinciden == 0; i++) { if(limInf <= Padre && Padre < limSup) { Padre = i; coinciden = 1; 142 } else { limInf = limInf + fitness[i]; limSup = limSup + fitness[i+1]; } } }//fin eligo_padre void Cruce(const int &m_var, const int &lMaxVsol, vector<vector<int> > &AuxPob, vector<int> &lReVsolIzq, vector<int> &lReVsolDer, vector<int> &AuxlRsol, vector<vector<int> > &PobIniIzq, vector<vector<int> > &PobIniDer) { int minimo, columna, longitud; for(int i= 0; i< m_var; i++) { AuxPob[i].resize(lMaxVsol); minimo= min(lReVsolIzq[i],lReVsolDer[i]); AuxlRsol[i]= minimo; for(int j= 0; j< minimo; j++) { columna= rand() % 2; if(columna == 0) AuxPob[i][j]= PobIniIzq[i][j]; else AuxPob[i][j]= PobIniDer[i][j]; }//fin for(j) longitud= lMaxVsol - minimo; columna= longitud +1; columna= rand() % columna; if(lReVsolIzq[i]< lReVsolDer[i]) { 143 for(int j= 0; j< columna; j++) { AuxPob[i][minimo+j]= PobIniDer[i][minimo+j]; if(AuxPob[i][minimo+j]!= 2) AuxlRsol[i]= AuxlRsol[i] +1; }//fin for(j) for(int j= columna; j< longitud; j++) AuxPob[i][minimo+j]= 2; }//fin if else { for(int j= 0; j< columna; j++) { AuxPob[i][minimo+j]= PobIniIzq[i][minimo+j]; if(AuxPob[i][minimo+j]!= 2) AuxlRsol[i] = AuxlRsol[i] +1; }//fin for(j) for(int j= columna; j< longitud; j++) AuxPob[i][minimo+j] = 2; }//fin else }//fin for(i) }//fin cruce void Mutacion(const int &m_var, const int &lMaxVsol, int &muto, vector<vector<int> > &AuxPob, vector<int> &AuxlRsol) { for(int i= 0; i< m_var; i++){ for(int j= 0; j< lMaxVsol; j++) { muto= rand() % lMaxVsol; if(muto == 0) AuxPob[i][j]= rand() % 3; }//fin for (j) AuxlRsol[i]= lMaxVsol; reordeno(lMaxVsol, AuxPob[i],AuxlRsol[i]); }//fin for(i) }//fin Mutacion 144 Apéndice B El Algoritmo Genero Problema // //////////////////////////////////////////////////////////// // // / // / / // Un Algoritmo que Genera Sistemas de ‘‘Word Ecuations’’ // / / // / // // //////////////////////////////////////////////////////////// // #include<fstream.h> #include<stdlib.h> #include<vector.h> #include<stdio.h> #include<math.h> #include<string.h> using namespace std; void Pedir_Datos(string &sistema, string &soluciones, int &n_ec, int &lmax_ec, int &m_var, int &lmax_var); void Genero_Cadenas(const int &SumaLong, const int &lmax_ec, vector<int> &Ecizq, vector<int> &Ecder, int &l_ecizq, int &l_ecder); void ruleta(const int &SumaLong, const int &lmax_ec, int &l_ec); 145 void Def_variable(const int &n_ec, const int &m_var, const int &lmax_var, vector<int> &l_ec, vector<vector<int> > &Ec, vector<int> &l_var, vector<vector<int> > &Var, int &contador); void insertar(const int &k,int &posicion, int &a, vector<int> &v); void sustituir(const int &k, int &posicion, int &a, const int &b,vector<int> &v); void recorro_ec(int &contador, const int &k, int &l_ec, const int &lmax_var, const int &l_var, const vector<int> &Var, vector<int> &Ec); void mejora(int &l_ecizq, vector<int> &Ecizq, const int &SumaLong, int &l_ecder, vector<int> &Ecder, const int &lmax_var, const vector<int> &l_var, const vector<vector<int> > Var, const int &lmax_ec, const int &m_var); void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Ec); int main(void) { string sistema; string soluciones; int i, j, k, l, m; int probabilidad; int n_ec, lmax_ec, m_var, lmax_var; int indice, posicion, auxiliar, contador, coincidencia, coinciden; 146 int SumaLong; vector<vector<int> > Ec; vector<vector<int> > Var; vector<int> l_ec; vector<int> l_var; randomize(); Pedir_Datos(sistema, soluciones, n_ec, lmax_ec, m_var, lmax_var); fstream fprob(sistema.c_str(),ios::out); fstream fsol(soluciones.c_str(),ios::out); fsol<<"n_ec= "<<n_ec<<" lmax_ec= "<<lmax_ec<<" m_var= "<<m_var; fsol<<" lmax_var= "<<lmax_var<<endl; Ec.resize(2*n_ec); Var.resize(m_var); l_ec.resize(2*n_ec); l_var.resize(m_var); SumaLong= (lmax_ec +1)*lmax_ec / 2; for(i= 0; i< 2*n_ec -1; i+= 2) Genero_Cadenas(SumaLong, lmax_ec, Ec[i], Ec[i+1], l_ec[i], l_ec[i+1]); Def_variable(n_ec, m_var, lmax_var, l_ec, Ec, l_var, Var, contador); for(k= 0; k< m_var && contador> 0; k++) for(i= 0;i< 2*n_ec; i++) 147 recorro_ec(contador, k, l_ec[i], lmax_var, l_var[k], Var[k], Ec[i]); if(contador <= 0) { fprob<<"El programa ha fracasado."<<endl; fprob<<"No hemos podido generar un sistema de"<<endl; fprob<<n_ec<<" ecuaciones con "<<m_var<<" variables."<<endl; indice = k; for(l= indice; l< m_var; l++) { l_var[l]= 0; Var[l].resize(l_var[l]); }//fin for(l) }//fin if for(i= 0; i< 2*n_ec -1; i+= 2) mejora(l_ec[i], Ec[i], SumaLong, l_ec[i+1], Ec[i+1], lmax_var, l_var, Var, lmax_ec, m_var); Simplifica(n_ec, l_ec, Ec); if(contador >= 0) { fprob<<n_ec<<endl; fprob<<m_var<<endl; for(i= 0; i< 2*n_ec; i++) { fprob<<l_ec[i]<<" "; for(j= 0; j<l_ec[i]; j++) fprob<< Ec[i][j]<<" "; fprob<<endl; }//fin for(i) fsol<<"las soluciones son :"<<endl; 148 for(i= 0; i< m_var; i++) { if(l_var[i] == 0) fsol<<"2"; else for(j= 0; j< l_var[i]; j++) fsol<<Var[i][j]<<" "; fsol<<endl; }//fin for(i) }//fin if(contador >= 0) }//fin programa void Pedir_Datos(string &sistema, string &soluciones, int &n_ec, int &lmax_ec, int &m_var, int &lmax_var) { cout<<"Indique el fichero donde se guardara el sistema generado:"<<endl; cin>> sistema; cout<<"Indique el fichero donde se guardaran las soluciones: "<<endl; cin>> soluciones; do{ cout<<"Indique el numero de ecuaciones(>0) que desea construir:"<<endl; cin>> n_ec; cout<<endl; }while ( n_ec <= 0 ); cout<<"Definimos la longitud de una ecuacion como: "<<endl; cout<<"’numero de simbolos que aparecen en cada lado de la ecuacion’."<<endl; cout<<endl; do{ cout<<"Indique la longitud maxima(>0) de las ecuaciones del sistema:"<<endl; cin>> lmax_ec; cout<<endl; }while ( lmax_ec <= 0 ); cout<<"El sistema constara de al menos una variable."<<endl; do{ 149 cout<<"Indique el numero de variables(>0): "<<endl; cin>> m_var; cout<<endl; }while ( m_var <= 0 ); do{ cout<<"Indique la longitud maxima(>0) de las variables: "<<endl; cin>> lmax_var; cout<<endl; }while ( lmax_var <= 0 ); }//fin void Pedir_Datos void Genero_Cadenas(const int &SumaLong, const int &lmax_ec, vector<int> &Ecizq, vector<int> &Ecder, int &l_ecizq, int &l_ecder) { ruleta(SumaLong,lmax_ec,l_ecizq); l_ecder= l_ecizq; Ecizq.resize(l_ecizq); Ecder.resize(l_ecder); for(int j= 0; j< l_ecizq; j++) { Ecizq[j]= rand() % 2; Ecder[j]= Ecizq[j]; }//fin for(j) }//fin void Genero_Cadenas void ruleta(const int &SumaLong, const int &lmax_ec, int &l_ec) { int aux, sum, limInf, limSup; aux = rand() % SumaLong; sum = 1; limInf = 0; 150 limSup = sum; l_ec = 0; for(int j= 0; j< lmax_ec && l_ec == 0; j++) if(limInf<= aux && aux< limSup) l_ec = j +1; else { sum= sum +1; limInf= limSup; limSup= limSup +sum; }//fin else }//fin void ruleta void Def_variable(const int &n_ec, const int &m_var, const int &lmax_var, vector<int> &l_ec, vector<vector<int> > &Ec, vector<int> &l_var, vector<vector<int> > &Var, int &contador) { int auxiliar, indice, posicion; contador = 0; for(int i= 0; i< 2*n_ec; i++) contador = contador + l_ec[i]; for(int k= 0; k< m_var && contador >0; k++) { do{ indice = 2 * n_ec; indice = rand() % indice; posicion = rand() % l_ec[indice]; }while(Ec[indice][posicion]!=0 && Ec[indice][posicion]!=1); auxiliar = 0; for(int j= posicion; j< l_ec[indice] && (Ec[indice][j]==0 || Ec[indice][j]==1); j++) auxiliar = auxiliar + 1; auxiliar = min(lmax_var,auxiliar) +1; 151 l_var[k] = rand() % auxiliar; Var[k].resize(l_var[k]); if(l_var[k]==0) insertar( k, posicion, l_ec[indice], Ec[indice]); else { auxiliar = 0; for(int j= posicion; j< posicion +l_var[k]; j++) { Var[k][auxiliar]= Ec[indice][j]; auxiliar= auxiliar +1; }//fin for(j) sustituir(k, posicion, l_ec[indice], l_var[k], Ec[indice]); }//else contador = contador -l_var[k]; }//fin for(k) }//fin void Def_variable void insertar(const int &k,int &posicion, int &a, vector<int> &v) { a = a+1; v.resize(a); for(int l = a-1; l> posicion; l--) v[l] = v[l-1]; v[posicion] = k+3; }//fin insertar void sustituir(const int &k,int &posicion,int &a,const int &b, vector<int> &v) { v[posicion]= k+3; a = a -b +1; for(int l= posicion; l< a-1; l++) v[l+1]=v[l+b]; v.resize(a); 152 }// fin sustituir void recorro_ec(int &contador, const int &k, int &l_ec, const int &lmax_var, const int &l_var, const vector<int> &Var, vector<int> &Ec) { int coincidencia, probabilidad, auxiliar, posicion; coincidencia = 1; for(int j= 0; j< l_ec && coincidencia == 1; j++) if(l_var==0) { if((Ec[j] == 0 || Ec[j] == 1) && (Ec[j-1] != k+3)) { probabilidad = 4*lmax_var; probabilidad = rand() % probabilidad; if(probabilidad == 0) { insertar( k, j, l_ec, Ec); j=j+1; }//fin if }//fin if } else { auxiliar=l_ec-j; if(l_var > auxiliar) coincidencia = 0; else { auxiliar = 0; coincidencia = 1; posicion = j; for(int l= posicion;l< posicion +l_var && coincidencia == 1; l++) 153 if(Ec[l] != Var[auxiliar]) coincidencia = 0; else auxiliar = auxiliar + 1; }//fin else if(coincidencia == 1) { // * Cambio con probabilidad l_var[k]/lmax_var * probabilidad = rand() % lmax_var; probabilidad = probabilidad + 1; if(probabilidad <= l_var) { sustituir( k, posicion, l_ec, l_var, Ec); contador = contador - l_var; }//fin if(probabilidad <= l_var) }//fin if(coincidencia == 1) }//fin else }//fin recorro_ec void mejora(int &l_ecizq, vector<int> &Ecizq, const int &SumaLong, int &l_ecder, vector<int> &Ecder, const int &lmax_var, const vector<int> &l_var, const vector<vector<int> > Var, const int &lmax_ec, const int &m_var) { int coinciden, contador, cont_izq, cont_der; do{ coinciden= 0; cont_izq = 0; for(int j= 0; j< l_ecizq; j++) if(Ecizq[j]!=0 && Ecizq[j]!=1) cont_izq = cont_izq +1; if(cont_izq == 0) for(int k= 0; k< m_var && contador> 0; k++) recorro_ec(cont_izq, k, l_ecizq, lmax_var, l_var[k],Var[k], Ecizq); 154 cont_der = 0; for(int j= 0; j< l_ecder; j++) if(Ecder[j]!=0 && Ecder[j]!=1) cont_der = cont_der +1; if(cont_der == 0) for(int k= 0; k< m_var && contador> 0; k++) recorro_ec(cont_der, k, l_ecder, lmax_var, l_var[k],Var[k], Ecder); if(l_ecizq == l_ecder) { coinciden= 1; for(int j= 0; j< l_ecizq && coinciden == 1; j++) if(Ecizq[j]!=Ecder[j]) coinciden=0; if(coinciden == 1) // * defino una nueva ecuacion * { Genero_Cadenas(SumaLong,lmax_ec,Ecizq,Ecder,l_ecizq,l_ecder); contador= 2*l_ecizq; for(int k= 0; k< m_var && contador> 0; k++) { recorro_ec(contador, k, l_ecizq, lmax_var, l_var[k], Var[k], Ecizq); recorro_ec(contador, k, l_ecder, lmax_var, l_var[k], Var[k], Ecder); }//fin for(k) }//fin if(coinciden == 1) }//fin if(l_ecizq == l_ecder) }while(coinciden == 1 || cont_izq == 0 || cont_der == 0); }//fin mejora void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Ec) { int coincidencia, columnas, minimo; 155 for(int i= 0; i< 2*n_ec - 1; i+= 2) { coincidencia= 0; columnas= 0; minimo= min (l_ec[i],l_ec[i+1]); while(Ec[i][columnas]== Ec[i+1][columnas] && columnas< minimo) { coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i]-coincidencia; for(int j= 0; j< l_ec[i]; j++) Ec[i][j]= Ec[i][j+coincidencia]; Ec[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; for(int j= 0; j< l_ec[i+1]; j++) Ec[i+1][j]= Ec[i+1][j+coincidencia]; Ec[i+1].resize(l_ec[i+1]); }//fin if coincidencia= 0; columnas= 1; minimo= min (l_ec[i],l_ec[i+1]); while(columnas<= minimo && Ec[i][l_ec[i]-columnas] == Ec[i+1][l_ec[i+1]-columnas]) { coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i] -coincidencia; Ec[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; Ec[i+1].resize(l_ec[i+1]); }//fin if }//fin for(i) }//fin Simplifica 156 Apéndice C Los problemas propuestos En el capı́tulo de experimentos hacemos referencia a una cadena de problemas que hemos seleccionado para estudiar el comportamiento del algoritmo genético diseñado. Estos son algunos de los problemas propuestos: C.1. El problema 10-15-3 Sistema de 10 “word equations” con 15 variables de longitudes comprendidas entre 0 y 3, siendo al menos una de ellas una cadena de longitud 3. x1 x13 x4 x7 = x1 x2 x11 x6 x13 x4 x4 x1 x1 x1 x11 = x1 x4 1x6 x1 x13 x4 x1 x6 x5 x1 = 0x1 x1 x10 x1 1 = x11 x11 x8 10x6 x9 x12 x13 = 0x2 1x4 x1 0x1 x15 x13 x1 x4 x1 0x5 0x2 x1 x6 = x11 x6 1x4 x6 x1 x12 x13 1x1 x4 x1 1x4 0 = x2 x8 1x1 x3 x4 x12 0 1x2 x13 x6 x1 10 = x4 0x1 x8 0x2 0x4 x13 0x4 x3 10x2 x6 = x8 x11 x8 x3 1x1 x12 0x1 x5 x6 x1 x1 x13 = x2 x5 x14 x7 0 157 C.2. El problema 10-15-5 Sistema de 10 “word equations” con 15 variables de longitudes comprendidas entre 0 y 5, siendo al menos una de ellas una cadena de longitud 5. x3 x6 x5 x17 01 = x12 1x15 x16 1x12 00x11 1x9 x14 x1 x10 x5 0x9 0 = x10 x5 x9 x17 0x12 1x12 x3 x4 x8 x9 0 = x17 1x12 0x15 x5 x16 0x4 1x9 x17 0 x9 x11 1 = x9 1 0 = x16 0 x6 x3 x12 x10 x10 = 1x16 10x9 x15 x3 x5 x11 01x5 0x7 0 = x8 x5 x3 x11 x17 0 1x9 0x9 0x9 x4 x13 = x10 x9 00x11 x12 0x14 00 x12 = x4 x9 10x13 1x3 0x10 x7 0 = 1x9 x8 101x6 C.3. El problema 10-3-3 Sistema de 10 “word equations” con 3 variables de longitudes comprendidas entre 0 y 3, siendo al menos una de ellas una cadena de longitud 3. x4 00100x3 01x3 = 1000100x3 01x3 x4 x4 0101x4 x5 0 = x4 10010x3 1010 00x3 = 00110 0x5 x4 01x4 0x3 0 = 0x4 x3 0x3 0x3 0 11x3 00x4 = 11x3 0010 1001 = x4 01 10001x3 x4 1x3 0 = x4 001x3 x5 x3 0 x4 x4 01000010 = x4 x4 010000x4 010x4 000000 = 0x4 10000000 1100010x4 x4 1 = 11000x5 0x5 La solución propuesta es: x3 = 110 x4 = 10 158 x5 = 101 C.4. El problema 10-5-1 Sistema de 10 “word equations” con 5 variables de longitudes comprendidas entre 0 y 1, siendo al menos una de ellas una cadena de longitud 1. x3 x6 1x3 x6 1x3 1 = x4 x6 1x6 1x5 x3 1x6 1x4 0x4 0 = x3 1x6 1x6 0x3 0 x3 11x4 x6 1x3 01 = 5x5 x3 1x3 x6 0x3 1 x4 x6 0x4 10x3 00 = x7 x6 1x6 00x6 0 x3 101x6 0 = x5 x4 0x1 0 x3 0x6 11x7 0 = x3 x6 0x3 1100 x4 0x3 0x6 0 = x7 x3 x4 0x3 0 x4 x6 100x3 1 = x5 x3 00x4 1 x7 x6 0x4 x6 0x3 0 = x3 x4 000x3 0 x7 x3 1x3 x6 10x3 0 = x7 x3 x6 1x3 x4 x6 10x3 0 La solución propuesta es: C.5. x3 = Λ x4 = Λ x5 = 1 x6 = Λ x7 = 0 El problema 10-5-2 Sistema de 10 “word equations” con 5 variables de longitudes comprendidas entre 0 y 2, siendo al menos una de ellas una cadena de longitud 2. x4 1x4 = x6 1 x4 0x4 010x4 1x4 1x4 = x5 x4 0x4 1011 0x5 0 = x5 0x4 0x6 1x6 = x4 1 x4 x6 111 = 111x4 x7 1x4 = x5 1 x6 11x5 = 110 x4 = Λ x6 0x5 x5 0x4 x6 00 = 0x7 x7 x7 x5 x5 x3 x7 = x6 100 159 La solución propuesta es: C.6. x3 = 10 x4 = Λ x5 = 0 x6 = Λ x7 = 0 El problema 10-8-3 Sistema de 10 “word equations” con 8 variables de longitudes comprendidas entre 0 y 3, siendo al menos una de ellas una cadena de longitud 3. 1x8 0x7 = 1x9 01 0x3 = 0x4 x8 1 x5 011 = x5 0x7 x5 x7 x9 0x3 000 = 0x3 000 0x4 0x4 0x9 0x5 0 = x9 0x5 x10 x8 x10 00 00100x9 0 = 00x10 00 x6 0 = x4 x3 0 x4 x10 000 = x3 000x5 0 x10 x8 0x9 00x5 0 = x4 00x9 0x9 00 0x3 x5 0x5 1 = 0x8 x7 x4 01 C.7. El problema 10-8-5 Sistema de 10 “word equations” con 8 variables de longitudes comprendidas entre 0 y 5, siendo al menos una de ellas una cadena de longitud 5. 0011x3 111 = 0x3 0x8 x8 1 x4 x9 x3 1 = x4 0x4 11 01x7 x5 0111 = x5 x3 x10 x3 00011x3 x1 0x9 1x5 001 = x3 00x8 x3 x4 000x4 1 x8 1x5 1x6 11 = x8 1x3 01x6 x8 0x5 1x5 x5 x5 = x5 01x5 00 000x9 x3 0 = x5 0x5 0x4 10 0x3 x4 x10 x5 = x3 x9 x5 x5 010 1x9 1x7 10 = 101x10 x9 0 x5 1x5 0 = 0x3 10x5 160 C.8. El problema 12-6-4 Sistema de 12 “word equations” con 6 variables de longitudes comprendidas entre 0 y 4, siendo al menos una de ellas una cadena de longitud 4. x5 01x8 = x5 0x7 10 111 = 1x7 11 x4 1x8 101x8 1x8 = 1010x4 x7 10x4 1x8 x7 11x5 00 = 11x5 00 x8 11x3 10 = 0x4 11x3 x4 10 101x8 0110x4 11 = 1010011011 x8 0x4 1 = 0x8 1 1x6 x7 01x4 100 = 1x6 x4 x7 0x6 x7 x8 11x7 0x3 1 = 0110x3 1 x4 x6 1 = x6 1 0x4 x7 10x4 x7 1001 = 01x4 01001 000x3 = 0x8 0x3 C.9. El problema 15-12-4 Sistema de 15 “word equations” con 12 variables de longitudes comprendidas entre 0 y 4, siendo al menos una de ellas una cadena de longitud 4. 11x5 x13 x3 x9 x12 x3 x10 x13 = 1x6 11x5 0x9 x12 1x9 0 0x7 0000 = 00x3 x9 000 1x13 0x3 x12 0x12 x13 x5 11 = x10 1x5 x6 x12 x8 011x9 0x12 x3 x5 x12 x4 x4 0 = x5 0x6 100x5 100 x6 11x4 11x3 x4 x3 00x12 = x6 1x9 x12 x4 1x12 0x12 0x5 x7 x8 x6 x10 1x13 0x4 x7 0x8 = x6 x10 x12 x3 x10 x14 10x3 x14 0x4 x5 0x9 0x3 0x5 x7 1 x9 10x12 x5 x13 x4 = x7 x13 1x12 0x4 x6 x5 0x4 x14 0x3 x12 = x3 x8 001 0x4 x7 1x9 1x6 0x5 0 = x14 00x6 10x3 1x1 41x9 00 x5 0x3 01x3 0 = 0x3 x4 x4 1x6 1x3 x14 01x6 x7 1x10 x12 = x4 1x7 1x14 0x12 x12 1 161 x5 0x11 00 = 011x7 x14 1x6 0x5 00 x10 011 = 0x12 x5 1 1x9 1x13 0x14 11 = 1x10 x141x3 x13 x3 0x14 1x9 1 x12 x12 x4 x5 0x3 x14 1x7 x12 x4 = x12 10x12 00x9 1x3 1x4 C.10. El problema 15-25-5 Sistema de 15 “word equations” con 25 variables de longitudes comprendidas entre 0 y 5, siendo al menos una de ellas una cadena de longitud 5. x17 01000100x24 1x18 = x3 x20 11x16 0x20 1x3 00x20 0100111 x8 10x26 01x20 000x10 = x3 x10 01x18 01x12 x19 001x20 10x23 0 1x4 x4 1x25 1x14 = x11 x3 11x18 1x19 1x12 111x10 0x19 x8 x12 1x10 = 0x20 1 x14 x16 0x25 11x19 011x3 = x19 x20 1x3 0x10 011011 x11 11x10 x19 1x10 0x15 x5 = x4 x4 00x12 x16 x20 01x20 1x25 110 x5 1x20 1 = x25 1x4 011x12 x25 x10 x8 x9 = x25 01x18 x25 0x18 1 x15 00001x18 0x10 00x3 x12 11x21 = 0x12 0x10 1000x19 0x18 1x13 11x12 x20 1x16 1 x11 x11 x16 1x16 10 = 1x22 x12 1x16 x19 0x20 01x10 0x20 1x18 101x12 1x20 0x19 1x3 = x14 010x25 110110x16 1 010x18 00x19 1x16 0x11 x6 = x3 x20 0x16 x20 1x27 0x12 1x10 01x18 010x10 x12 1 1x3 x19 1010011x12 x25 0x16 100x10 = x4 x3 x12 0x3 x10 10x18 0x16 x20 1101x19 00x25 x20 01x7 x12 1 = x6 1x17 x19 10x25 010x25 10x8 = 100x20 10100 162 C.11. El problema 15-7-5 Sistema de 15 “word equations” con 7 variables de longitudes comprendidas entre 0 y 5, siendo al menos una de ellas una cadena de longitud 5. 10x9 00x4 0000 = 10x7 x4 000000 x8 x3 1 = 10x3 x9 x4 0x7 = 001 x7 x5 0000x4 x9 = x7 x5 000001 00010 = 000x9 x4 11x6 0x4 x4 0 = x9 1x6 0x4 0x4 x8 10x8 0x5 = 1010x7 00x5 x9 = 1 x4 00x8 x4 11 = 0001x6 0x7 x4 1x4 = 0x9 0x7 0 0x7 11x4 x7 = 01x9 101 x7 0x4 x9 0x8 0 = x9 00x7 01x4 0 x5 x8 0001 = x5 100001 x6 0 = 001x7 x4 0x5 x5 1 = 0x5 x5 x7 163 C.12. El problema 25-23-4 Sistema de 25 “word equations” con 23 variables de longitudes comprendidas entre 0 y 4, siendo al menos una de ellas una cadena de longitud 4. x9 x13 x7 x14 x9 x3 0x7 x5 x13 x19 x25 = x9 x7 x6 x23 0x9 x5 x25 x5 x9 x4 1x9 x22 x7 x7 x3 x13 x3 x9 x6 x9 x13 x4 x22 = x22 x4 x4 x16 x21 x3 x9 x13 x4 x18 x6 0 0x7 0x14 0x21 x4 0 = x24 010x15 x10 x25 x7 x13 x5 x22 x5 x10 10x4 x7 x22 x12 x3 = x13 01x4 x20 x22 x9 x22 1x24 x25 x3 x16 x3 x9 = x9 x14 0x3 x25 x3 x3 1x4 x3 = x4 x4 x3 x21 x9 x4 x14 x3 x7 x4 x3 x4 x7 x9 = x10 x23 1x18 x12 x3 x4 x19 1x3 x6 x22 x6 0x9 = 1x15 x22 x9 x18 x22 x18 x13 = x16 x10 x25 1 1x10 x18 x3 0x6 x24 0x10 x19 x21 x3 = x5 1x3 x3 x4 x4 x9 x13 x22 0 1x9 1 = x23 1x17 x7 1x22 x14 x25 1x16 x15 x3 x15 = x8 x7 x18 x10 x15 x3 x4 0x9 x4 x7 x24 00x5 x4 x22 x6 x9 x8 x14 x16 = x7 x10 0x22 x13 0x8 x7 x4 x10 0x5 x13 x9 0 x3 x3 x6 x4 0x14 x5 x15 = x3 x11 x4 0x6 x6 x7 x4 = x4 x4 x4 x21 x6 x9 x9 x18 x4 x7 x6 x4 1x4 x5 x14 x4 x25 = x14 x4 x3 0x5 1x5 x7 x4 x5 x7 x5 x21 x13 x4 x4 x21 0 x15 x4 x4 = x5 x10 x14 x5 x7 x13 x5 1x5 x9 x21 = x15 x25 x7 x21 = x15 x21 x3 x6 x25 x7 = x7 x18 x9 x13 x10 x7 x17 x7 x4 x9 x4 10x19 x15 x16 x15 x4 x18 = x13 x4 0x4 x13 0x4 00x7 x5 x13 0 x7 x5 0x14 x4 x6 = x24 x5 x24 1x22 x6 x13 x4 0x7 x9 x7 x18 x4 0x6 x5 x7 x5 x7 x5 x7 x6 = 0x7 x9 x21 x25 1x18 x15 x5 x6 x12 x4 x14 x4 x3 x5 x13 x10 x12 x12 x3 x3 0x7 = x7 x9 x10 0x7 x4 x4 x6 x16 x25 x9 x22 x14 x21 x22 x15 = x4 x9 x13 164 C.13. El problema 25-8-3 Sistema de 25 “word equations” con 8 variables de longitudes comprendidas entre 0 y 3, siendo al menos una de ellas una cadena de longitud 3. x7 0 = x6 x8 x5 00 1x3 x3 x10 x7 x4 x3 x9 x9 = 1x8 x3 1x3 x3 x4 0x3 x3 x9 = 0 11x4 x8 x3 x6 1x8 x5 0 = x6 x10 x10 x3 x10 x10 x3 x3 x10 0x6 0 x3 x5 x9 1x3 x3 0 = x3 x8 1x7 x8 x10 1x10 x3 x10 x10 x3 x7 x5 x8 x3 x10 x8 = x10 11x4 x9 0x3 x3 x8 1x3 1x4 01x8 x9 x3 x3 = 1x4 01x3 x8 x3 x9 1x6 1x3 x3 x10 x8 = 1x10 x7 1x3 x5 1x8 = x6 1x3 x6 x8 x6 0x4 x4 x10 x10 x4 1 = 0x3 x3 1x6 1x4 x10 1x5 x4 x10 x7 x6 10x5 1x3 1x6 x8 x3 0x3 x9 1x9 = x9 x9 x10 x9 101x3 x3 x5 x7 0x10 x9 x9 x10 x9 x3 1x8 = x3 1x3 x8 1x3 11x4 0x7 x3 0x10 0 = x10 1x4 0x3 0x7 x10 x3 11x3 x8 x10 = 11x8 x3 1 1x3 01x3 = x10 x8 x3 x10 x5 0 0x8 0x10 x4 x10 x7 x10 x9 1 = 0x9 x3 1x4 x10 x7 1x3 1 x8 x3 11x3 11x10 x10 = x8 x3 x10 x5 x10 x4 1x5 x6 1 x1 0x8 x7 = x10 x3 x7 x10 x10 x9 0x3 = 1x5 x6 x10 0x7 x9 = 0 x4 1x7 1 = x3 x10 11x7 1 x10 x8 x3 x5 0x8 x3 x3 x4 = x10 x7 0x3 x9 x9 x3 x10 x10 x7 1x4 1 = x3 x3 1x4 x10 x8 x7 0x5 0 = 0x5 x6 x9 x3 x6 x7 101x5 x7 10x9 x3 = 1x3 1x7 1x5 0x5 0x8 165 C.14. El problema 25-8-5 Sistema de 25 “word equations” con 8 variables de longitudes comprendidas entre 0 y 5, siendo al menos una de ellas una cadena de longitud 5. 1x3 x5 0 = x6 x9 0x10 x4 01000x4 000x4 01110 = 0x10 00x9 11x4 0 1x8 = x6 0 11x8 1x3 0 = 1x6 01x3 0 1x9 0x4 100 = 1000x4 1x4 0x10 x8 110x3 11 = x8 x4 110x3 11 x7 111x6 = x7 1111x4 110 00x10 x4 001 = 00100001 0101x4 x6 01x4 1x4 1 = 0101x6 0111 x10 00 = 10000 001x4 0 = 0010 0x6 0x6 1000 = 0x6 01x7 00 1x4 10x10 x10 111 = x7 x4 0x10 111 x4 1 = 1 x9 000 = 000x4 1x4 000 1011110x5 1 = 101x6 x5 1 110x4 101001 = x7 1001 11011111x6 = 110x4 11111111x4 0 x4 1x7 00000 = x6 10x4 00000 111111x4 10 = 1111x6 x10 x4 001x3 011 = x4 10x4 x9 x3 011 00001x3 1x4 11 = 00001x3 111 x4 000 = 000 x4 0x10 00x9 x9 = 0x10 000x4 001x9 0001x4 00 = 000x10 166 C.15. El problema 5-15-3 Sistema de 5 “word equations” con 15 variables de longitudes comprendidas entre 0 y 3, siendo al menos una de ellas una cadena de longitud 3. x1 210110x15 0x16 0 = 001011001x8 x6 1110x9 1000100 = x5 1x3 1x5 1x11 00x9 0100x11 x4 x4 0x9 1x13 = x9 x14 1x9 01x7 x3 01001000x11 1001x6 = x4 01x9 0010001x11 0010 x10 x10 x11 0x9 0100 = x4 x17 x10 001x11 00x9 C.16. El problema 5-3-2 Sistema de 5 “word equations” con 3 variables de longitudes comprendidas entre 0 y 2, siendo al menos una de ellas una cadena de longitud 2. 10 = x3 x4 x5 1 = 0x3 1x3 x3 011001 = 011001x3 x3 0 = x5 x5 = x3 0 La solución propuesta es: x3 = Λ x4 = 10 x5 = 0 167 Apéndice D A.E. Sólo Mutación // //////////////////////////////////////////////////////////// // // / / // // / Un Algoritmo Evolutivo para Resolver el problema l_SWES / // // / / // // / (SÓLO MUTACIÓN) / // // / / // // //////////////////////////////////////////////////////////// // #include<fstream.h> #include<stdlib.h> #include<vector> #include<stdio.h> #include<math.h> #include<string.h> using namespace std; fstream fsol; void Pedir_Datos(string &problema, int &lMaxVsol, int &NMaxIter, int &Metodo); void Lee_Prob(const string &problema, int &n_ec, int &m_var, vector<int> &l_ec, vector<vector<int> > &Sist); 168 void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Sist); void Genera_PobIni(vector<vector<int> > &PobIni, vector<int> &lReVsol, vector<vector<int> > &AuxPob, const int &m_var, const int &lMaxVsol, const int &Metodo); void reordeno(const int &l, vector<int> &v, int &lReVsol); void Evalua(const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, const vector<int> &lReVsol, const vector<vector<int> > &PobIni, vector<int> &AuxIzq, vector<int> &AuxDer, int &fitness, int &haysol); void sustituyo(const vector<int> &Sist, int &lAux, vector<int> &Aux, const vector<int> &lVsol, const vector<vector<int> > &PobIni); void Mutacion(const int &m_var, const int &lMaxVsol, vector<vector<int> > &AuxPob, vector<int> &AuxlRsol); int main() { string problema; int lMaxVsol, NMaxIter, Metodo, Npruebas; int n_ec, m_var; int fitness, AuxFit, haysol; int i, j, k, iter, npru; vector<int> l_ec; 169 vector<vector<int> > Sist; vector<vector<int> > PobIni; vector<int> lReVsol; vector<vector<int> > AuxPob; //vector auxiliar vector<int> AuxlRsol; //vector auxiliar vector<int> AuxIzq; //Sólo se utiliza en la función evalua vector<int> AuxDer; //Sólo se utiliaza en la función evalua randomize(); Pedir_Datos(problema, lMaxVsol, NMaxIter, Metodo); Lee_Prob(problema, n_ec, m_var, l_ec, Sist); PobIni.resize(m_var); lReVsol.resize(m_var); AuxPob.resize(m_var); //vector auxiliar AuxlRsol.resize(m_var); //vector auxiliar Simplifica(n_ec, l_ec, Sist); Genera_PobIni(PobIni, lReVsol, AuxPob, m_var, lMaxVsol, Metodo); haysol= 0; Evalua(n_ec, l_ec, Sist, lReVsol, PobIni, AuxIzq, AuxDer, fitness, haysol); fsol<<"La poblacion inicial generada es: "<<endl; for(i= 0; i<m_var; i++) { for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[i][j]; fsol<<" long="<<lReVsol[i]<<" fsol<<" "; fsol<<"Con fitness "<<fitness<<endl; 170 "; }//fin for(i= 0; i< m_var; i++) {AuxlRsol[i]= lReVsol[i]; for(j= 0;j< lMaxVsol; j++) AuxPob[i][j]= PobIni[i][j]; }//fin for(i) for(iter= 0; iter< NMaxIter && haysol == 0; iter++) { Mutacion(m_var, lMaxVsol, AuxPob, AuxlRsol); Evalua(n_ec, l_ec, Sist, AuxlRsol, AuxPob, AuxIzq, AuxDer, AuxFit,haysol); if(AuxFit < fitness) { for(i= 0; i< m_var; i++) {lReVsol[i] = AuxlRsol[i]; for(j= 0;j< lMaxVsol; j++) PobIni[i][j] = AuxPob[i][j]; }//fin for(i) fitness = AuxFit; } else { for(i= 0; i< m_var; i++) {AuxlRsol[i] = lReVsol[i]; for(j= 0;j< lMaxVsol; j++) AuxPob[i][j] = PobIni[i][j]; }//fin for(i) AuxFit = fitness; } }//fin for(iter) if(haysol == 1) { fsol<<"El programa ha encontrado solucion con longitud máxima "<<endl; fsol<<"de las variables igual a "<<lMaxVsol<<" ."<<endl; 171 fsol<<"Para generar la poblacion inicial se uso el metodo_"<<Metodo<<endl; fsol<<"La solucion propuesta tras "<<iter<<" iteraciones es: "<<endl; for(i= 0; i< m_var; i++) { fsol<<"x"<<i<<" = "; if(lReVsol[i] == 0) fsol<<"la palabra vacia."<<endl; else {for(j= 0;j< lReVsol[i]; j++) fsol<<PobIni[i][j]<<" "; fsol<<endl;}//fin else }//fin for(i) }//fin if(haysol==1) if(haysol == 0) { fsol<<"El programa NO ha encontrado solucion."<<endl; fsol<<"Los datos propuestos fueron: "<<endl; fsol<<"Longitud máxima de las variables igual a "<<lMaxVsol<<" ."<<endl; fsol<<"Para generar la población inicial se uso el metodo_"<<Metodo<<endl; fsol<<"La candidata a solución generada tras "<<iter<<" iteraciones es:"<<endl; for(i= 0; i< m_var; i++) {for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[i][j]; fsol<<" ";}//fin for(i) fsol<<" "; fsol<<"Con fitness "<<fitness<<endl; }//fin if(haysol==0) }//fin programa void Pedir_Datos(string &problema, int &lMaxVsol, int &NMaxIter, int &Metodo) { cout<<"Indique el nombre del fichero que contiene el problema: "<<endl; cin>> problema; 172 do{ cout<<"Escriba la longitud maxima(>0) de las candidatas a solucion:"<<endl; cin>> lMaxVsol; }while(lMaxVsol<=0); do{ cout<<"Numero maximo de iteraciones que desea realizar : "<<endl; cin>>NMaxIter; }while(NMaxIter<0); cout<<"Eliga el metodo que desea utilizar para generar la poblacion"<<endl; cout<<"inicial."<<endl<<endl; do{ cout<<"Pulse 1 si quiere fijar la longitud real de la variable y "<<endl; cout<<"luego generar la variable con simbolos del alfabeto {0,1}."<<endl; cout<<endl; cout<<"Pulse 2 si quiere generar la variable en {0,1,2} y luego "<<endl; cout<<"calcular la longitud real de la variable."<<endl<<endl; cout<<"Pulse 3 si desea recibir mas informacion sobre los metodos"<<endl; cout<<"usados en la generacion de la poblacion inicial."<<endl<<endl; cin>> Metodo; if(Metodo == 3) { cout<<"El metodo-1 elige la longitud real de la variable que va a"<<endl; cout<<"generar entre cero (que corresponde a la palabra vacia) y "<<endl; cout<<"la longitud maxima propuesta. Una vez fijada la longitud, "<<endl; cout<<"genera la variable como una cadena de ceros y unos. "<<endl<<endl; cout<<"El metodo-2 genera la variable como una cadena de simbolos"<<endl; cout<<"pertenecientes al conjunto {0,1,2} de longitud la longitud"<<endl; cout<<"maxima propuesta. Despues reordena esta cadena, colocando"<<endl; cout<<"todos los doses al final de la misma (pues representan el "<<endl; cout<<"simbolo vacio). Finalmente, calcula el numero de ceros y "<<endl; cout<<"unos que aparecen en la cadena y que corresponde a la "<<endl; cout<<"longitud real de la variable generada."<<endl<<endl; 173 cout<<"Que metodo desea utilizar?"<<endl; cin>> Metodo; }//fin if }while(Metodo<1 || Metodo>2); }//fin Pedir_Datos void Lee_Prob(const string &problema, int &n_ec, int &m_var, vector<int> &l_ec, vector<vector<int> > &Sist) { ifstream fprob(problema.c_str(),ios::in); fprob>>n_ec; fprob>>m_var; l_ec.resize(2*n_ec); Sist.resize(2*n_ec); for(int i= 0; i< 2*n_ec; i++){ fprob>>l_ec[i]; Sist[i].resize(l_ec[i]); for(int j= 0; j< l_ec[i]; j++) fprob>>Sist[i][j]; }//fin for(i) }//fin Lee_Prob void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Sist) { int coincidencia, columnas, minimo; for(int i= 0; i< 2*n_ec - 1; i+= 2) { coincidencia= 0; columnas= 0; minimo= min (l_ec[i],l_ec[i+1]); while(Sist[i][columnas]== Sist[i+1][columnas] && columnas< minimo) {coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i]-coincidencia; for(int j= 0; j< l_ec[i]; j++) 174 Sist[i][j]= Sist[i][j+coincidencia]; Sist[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; for(int j= 0; j< l_ec[i+1]; j++) Sist[i+1][j]= Sist[i+1][j+coincidencia]; Sist[i+1].resize(l_ec[i+1]); }//fin if coincidencia= 0; columnas= 1; minimo= min (l_ec[i],l_ec[i+1]); while(columnas<= minimo && Sist[i][l_ec[i]-columnas] == Sist[i+1][l_ec[i+1]-columnas]) {coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) {l_ec[i]= l_ec[i] -coincidencia; Sist[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; Sist[i+1].resize(l_ec[i+1]); }//fin if }//fin for(i) }//fin Simplifica void Genera_PobIni(vector<vector<int> > &PobIni, vector<int> &lReVsol, vector<vector<int> > &AuxPob, const int &m_var, const int &lMaxVsol, const int &Metodo) { int longitud; for(int i= 0; i< m_var; i++) { PobIni[i].resize(lMaxVsol); AuxPob[i].resize(lMaxVsol); if(Metodo == 1) {longitud= lMaxVsol +1; lReVsol[i]= rand() % longitud; 175 for(int j= 0; j< lReVsol[i]; j++) PobIni[i][j]= rand() % 2; for(int j= lReVsol[i]; j< lMaxVsol; j++) PobIni[i][j]= 2; }//fin if(Metodo==1) if(Metodo == 2) {lReVsol[i]= lMaxVsol; for(int j= 0; j< lMaxVsol; j++) PobIni[i][j] = rand() % 3; reordeno(lMaxVsol, PobIni[i], lReVsol[i]); }//fin if(Metodo==2) }//fin for(i) }//fin Genera_PobIni void Evalua(const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, const vector<int> &lReVsol, const vector<vector<int> > &PobIni, vector<int> &AuxIzq, vector<int> &AuxDer, int &fitness, int &haysol) { int lAuxIzq, lAuxDer; int coincidencia, minimo; coincidencia = 0; for(int i= 0; i< n_ec; i++) {lAuxIzq= l_ec[2*i]; lAuxDer= l_ec[2*i+1]; AuxIzq.resize(lAuxIzq); AuxDer.resize(lAuxDer); sustituyo(Sist[2*i], lAuxIzq, AuxIzq, lReVsol, PobIni); sustituyo(Sist[2*i+1], lAuxDer, AuxDer, lReVsol, PobIni); minimo = min(lAuxIzq,lAuxDer); for(int j= 0;j< minimo; j++) if(AuxIzq[j] != AuxDer[j]) coincidencia= coincidencia+1; coincidencia= coincidencia + abs(lAuxIzq-lAuxDer); }//fin for(i) fitness= coincidencia; 176 if(fitness == 0) haysol= 1; }//fin Evaluacion void sustituyo(const vector<int> &Sist, int &lAux, vector<int> &Aux, const vector<int> &lVsol, const vector<vector<int> > &PobIni) { int auxiliar, variable; int j, l; auxiliar = 0; for(j= 0;j< lAux; j++) if(Sist[j+auxiliar]==0 || Sist[j+auxiliar]==1) Aux[j] = Sist[j+auxiliar]; else { variable = Sist[j+auxiliar] - 3; if(lVsol[variable]==0) {lAux=lAux-1; Aux.resize(lAux); auxiliar=auxiliar+1; j=j-1;} else {lAux=lAux+lVsol[variable]-1; Aux.resize(lAux); for(l= 0; l< lVsol[variable]; l++) Aux[j+l] = PobIni[variable][l]; auxiliar = auxiliar - lVsol[variable] + 1; j=j+lVsol[variable]-1;} }//fin else }//fin sustituyo void Mutacion(const int &m_var, const int &lMaxVsol, vector<vector<int> > &AuxPob, vector<int> &AuxlRsol) { 177 int muto; for(int i= 0; i< m_var; i++) { for(int j= 0; j< lMaxVsol; j++) {muto= rand() % lMaxVsol; if(muto == 0) AuxPob[i][j]= rand() % 3;}//fin for (j) AuxlRsol[i]= lMaxVsol; reordeno(lMaxVsol, AuxPob[i],AuxlRsol[i]); }//fin for(i) }//fin Mutación void reordeno(const int &l, vector<int> &v,int &lReVsol) { // Reordeno la variable y calculo la longitud real de la misma int columna; for(int j= 0; j< l-1; j++) if(v[j] == 2) { columna= j+1; while(v[columna] == 2 && columna< l-1) columna = columna +1; if(v[columna]!= 2) {v[j]= v[columna]; v[columna]= 2; }//fin if }//if(v[j] == 2) for(int j= 0; j< l; j++) if(v[j] == 2) lReVsol= lReVsol -1; }//fin reordeno 178 Apéndice E A.E. Búsqueda Local // //////////////////////////////////////////////////////////// // // / / // // / Un Algoritmo Evolutivo para Resolver el problema l_SWES / // // / / // // / (BÚSQUEDA LOCAL) // / / // / // // //////////////////////////////////////////////////////////// // #include<fstream.h> #include<stdlib.h> #include<vector.h> #include<stdio.h> #include<math.h> #include<string.h> using namespace std; fstream fsol("solucion.dat",ios::out); void Pedir_Datos(string &problema, int &TamPob, int &lMaxVsol, int &NMaxIter, int &ProMut, int &Metodo, int &Busqueda); void Lee_Prob(const string &problema, int &n_ec, int &m_var, vector<int> &l_ec, vector<vector<int> > &Sist); 179 void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Sist); void Genera_PobIni(vector<vector<vector<int> > > &PobIni, vector<vector<int> > &lReVsol, vector<vector<vector<int> > > &AuxPob, vector<vector<int> > &AuxlRsol, const int &TamPob, const int &m_var, const int &lMaxVsol, const int &Metodo); void reordeno(const int &l, vector<int> &v, int &lReVsol); void Evalua(const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, const vector<int> &lReVsol, const vector<vector<int> > &PobIni, vector<int> &AuxIzq, vector<int> &AuxDer, int &fitness, int &haysol, int &prueba); void sustituyo( const vector<int> &Sist, int &lAux, vector<int> &Aux, const vector<int> &lVsol, const vector<vector<int> > &PobIni); void Mejor_Sol(int &MejorFit, vector<int> &fitness, const int &TamPob, const int &m_var, vector<vector<int> > &AuxPob, const int &lMaxVsol, vector<vector<vector<int> > > &PobIni, vector<int> &AuxlRsol, vector<vector<int> > &lReVsol); void Ruleta(int &Sumfit, const int &TamPob, vector<int> &fitness); void eligo_padre(int &TamPob, int &Padre, vector<int> &fitness); void Cruce(const int &m_var, const int &lMaxVsol, 180 vector<vector<int> > &AuxPob, vector<int> &lReVsolIzq, vector<int> &lReVsolDer, vector<int> &AuxlRsol, vector<vector<int> > &PobIniIzq, vector<vector<int> > &PobIniDer); void Mutacion(const int &m_var, const int &lMaxVsol, int &muto, vector<vector<int> > &AuxPob, vector<int> &AuxlRsol); void Busqueda_loc_A(const int &m_var, vector<int> &lReVsol, const int &lMaxVsol, const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, vector<int> &AuxIzq, vector<int> &AuxDer, vector<vector<int> > &PobIni, int &fitness, int &haysol, int &cont_uno, int &cont_dos, int &prueba); void Busqueda_loc_B(const int &m_var, vector<int> &lReVsol, const int &lMaxVsol, const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, vector<int> &AuxIzq, vector<int> &AuxDer, vector<vector<int> > &PobIni, int &fitness, int &haysol, int &cont_uno,int &prueba); int main(void) { string problema; int TamPob, lMaxVsol, NMaxIter, ProMut, Metodo, Busqueda; int n_ec, m_var; int haysol, muto; int MejorFit, Sumfit, PadreIzq, PadreDer; int i, j, k, iter; int cont_uno, cont_dos; 181 int prueba; prueba=0; vector<int> l_ec; vector<vector<int> > Sist; vector<vector<vector<int> > > PobIni; vector<vector<int> > lReVsol; vector<vector<vector<int> > > AuxPob; //vector auxiliar vector<vector<int> > AuxlRsol; //vector auxiliar vector<int> fitness; vector<int> AuxIzq; //Sólo se utiliza en la función evalua vector<int> AuxDer; //Sólo se utiliaza en la función evalua cont_uno = 0; cont_dos = 0; randomize(); Pedir_Datos(problema, TamPob, lMaxVsol, NMaxIter, ProMut, Metodo, Busqueda); fsol<<problema<<" "<<TamPob<<" "<<lMaxVsol<<" "<<NMaxIter<<" "<<ProMut<<" "; fsol<<Metodo<<" "<<Busqueda<<endl; Lee_Prob(problema, n_ec, m_var, l_ec, Sist); Simplifica(n_ec, l_ec, Sist); Genera_PobIni(PobIni,lReVsol,AuxPob,AuxlRsol,TamPob,m_var,lMaxVsol,Metodo); fitness.resize(TamPob); haysol= 0; for(k= 0; k<TamPob; k++) Evalua(n_ec, l_ec, Sist, lReVsol[k], PobIni[k], AuxIzq, AuxDer, fitness[k], haysol,prueba); 182 fsol<<"La poblacion inicial generada es: "<<endl; for(k= 0; k<TamPob; k++) { for(i= 0; i< m_var; i++) { for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[k][i][j]; fsol<<" "; }//fin for(i) fsol<<" "; fsol<<"Con fitness "<<fitness[k]<<endl; }//fin for(k) if(Busqueda != 0) for(k= 0; k<TamPob; k++) if(Busqueda == 1) Busqueda_loc_A(m_var, lReVsol[k], lMaxVsol, n_ec, l_ec, Sist, AuxIzq, AuxDer, PobIni[k], fitness[k], haysol, cont_uno, cont_dos, prueba); else Busqueda_loc_B(m_var, lReVsol[k], lMaxVsol, n_ec, l_ec, Sist, AuxIzq, AuxDer, PobIni[k], fitness[k], haysol, cont_uno,prueba); fsol<<"La poblacion inicial tras la busqueda local es: "<<endl; for(k= 0; k< TamPob; k++) { for(i= 0; i< m_var; i++) { for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[k][i][j]; fsol<<" "; }//fin for(i) fsol<<" "; fsol<<"Con fitness "<<fitness[k]<<endl; }//fin for(k) for(iter= 0; iter<NMaxIter && haysol == 0; iter++) { Mejor_Sol(MejorFit, fitness, TamPob, m_var, AuxPob[0], lMaxVsol, PobIni, AuxlRsol[0], lReVsol); Ruleta(Sumfit, TamPob, fitness); for(k= 1; k< TamPob; k++) { // * Selección * 183 PadreIzq = rand() % Sumfit; eligo_padre(TamPob, PadreIzq, fitness); PadreDer = rand() % Sumfit; eligo_padre(TamPob, PadreDer, fitness); // * Cruce * Cruce(m_var, lMaxVsol, AuxPob[k], lReVsol[PadreIzq], lReVsol[PadreDer], AuxlRsol[k], PobIni[PadreIzq], PobIni[PadreDer]); // * Mutación * if(ProMut!=0) { muto = rand() %100; if(muto< ProMut) // * Muto la solusión k_esima * Mutacion(m_var, lMaxVsol, muto, AuxPob[k], AuxlRsol[k]); }//fin if(ProMut!=0) }//fin for(k) for(k= 0; k< TamPob; k++) for(i= 0; i< m_var; i++) { lReVsol[k][i]= AuxlRsol[k][i]; for(j= 0;j< lMaxVsol; j++) PobIni[k][i][j]= AuxPob[k][i][j]; }//fin for(i) fitness[0]= MejorFit; for(k= 1; k<TamPob; k++) Evalua(n_ec, l_ec, Sist, lReVsol[k],PobIni[k],AuxIzq, AuxDer, fitness[k] , haysol, prueba); if(Busqueda != 0) for(k= 0; k<TamPob; k++) if(Busqueda == 1) 184 Busqueda_loc_A(m_var, lReVsol[k], lMaxVsol, n_ec, l_ec, Sist, AuxIzq, AuxDer, PobIni[k], fitness[k], haysol, cont_uno, cont_dos, prueba); else Busqueda_loc_B(m_var, lReVsol[k], lMaxVsol, n_ec, l_ec, Sist, AuxIzq, AuxDer, PobIni[k], fitness[k], haysol, cont_uno,prueba); }//fin for(iter) if(haysol == 1) { fsol<<"El programa ha encontrado solucion con una poblacion de "<<endl; fsol<<"tama~ no "<<TamPob<<" y longitud máxima de las variables "<<endl; fsol<<"igual a "<<lMaxVsol<<" ."<<endl; fsol<<"La probabilidad de mutacion utiliza fue "<<ProMut<<" ."<<endl; fsol<<"Para generar la poblacion inicial se uso el metodo_"<<Metodo<<endl; fsol<<"La solucion propuesta tras "<<iter<<" iteraciones es: "<<endl; k=0; while(fitness[k] !=0) k= k + 1; for(i= 0; i< m_var; i++) { fsol<<"x"<<i<<" = "; if(lReVsol[k][i] == 0) fsol<<"la palabra vacia."<<endl; else { for(j= 0;j< lReVsol[k][i]; j++) fsol<<PobIni[k][i][j]<<" "; fsol<<endl; }//fin else }//fin for(i) }//fin if(haysol==1) if(haysol == 0) 185 { fsol<<"El programa NO ha encontrado solucion."<<endl; fsol<<"Los datos propuestos fueron: "<<endl; fsol<<"Tama~ no de poblacion igual a "<<TamPob<<" ."<<endl; fsol<<"Longitud máxima de las variables igual a "<<lMaxVsol<<" ."<<endl; fsol<<"La probabilidad de mutacion utilizada fue "<<ProMut<<" ."<<endl; fsol<<"Para generar la población inicial se uso el metodo_"<<Metodo<<endl; }//fin if(haysol==0) fsol<<"La poblacion generada tras "<<iter<<" iteraciones es: "<<endl; for(k= 0; k< TamPob; k++) { for(i= 0; i< m_var; i++) { for(j= 0;j< lMaxVsol; j++) fsol<<PobIni[k][i][j]; fsol<<" "; }//fin for(i) fsol<<" "; fsol<<"Con fitness "<<fitness[k]<<endl; }//fin for(k) if(Busqueda != 0) { float media; media = iter +1; media = cont_uno/media; fsol<<"contador_uno = "<<cont_uno<<" en media = "<<media<<endl; if(Busqueda == 1) { media = iter +1; media = cont_dos/media; fsol<<"contador_dos = "<<cont_dos<<" }//fin if 186 en media = "<<media<<endl; fsol<<"Busqueda= "<<Busqueda<<endl; }//fin if(Busqueda != 0) fsol<<"El numero de evaluaciones es "<<prueba<<endl; }//fin programa void Pedir_Datos(string &problema, int &TamPob, int &lMaxVsol, int &NMaxIter, int &ProMut, int &Metodo, int &Busqueda) { cout<<"Indique el nombre del fichero que contiene el problema: "<<endl; cin>> problema; do{ cout<<"Escriba el tamanio (>0) de la poblacion inicial:"<<endl; cin>> TamPob; }while(TamPob<=0); do{ cout<<"Escriba la longitud maxima(>0) de las candidatas a solucion:"<<endl; cin>> lMaxVsol; }while(lMaxVsol<=0); do{ cout<<"Numero maximo de iteraciones que desea realizar : "<<endl; cin>>NMaxIter; }while(NMaxIter<0); do{ cout<<"Cual es la probabilidad de mutacion que desea utilizar?"<<endl; cout<<"Indique esta probabilidad en porcentaje de 100"<<endl; cin>> ProMut; cout<<"Recuerde que se trata de un valor entero entre cero y 100."<<endl; cout<<endl<<endl; }while(ProMut>100 || ProMut<0); cout<<"Eliga el metodo que desea utilizar para generar la poblacion"<<endl; cout<<"inicial."<<endl<<endl; do{ 187 cout<<"Pulse 1 si quiere fijar la longitud real de la variable y "<<endl; cout<<"luego generar la variable con simbolos del alfabeto {0,1}."<<endl; cout<<endl; cout<<"Pulse 2 si quiere generar la variable en {0,1,2} y luego "<<endl; cout<<"calcular la longitud real de la variable."<<endl<<endl; cout<<"Pulse 3 si desea recibir mas informacion sobre los metodos"<<endl; cout<<"usados en la generacion de la poblacion inicial."<<endl<<endl; cin>> Metodo; if(Metodo == 3) { cout<<"El metodo-1 elige la longitud real de la variable que va a"<<endl; cout<<"generar entre cero (que corresponde a la palabra vacia) y "<<endl; cout<<"la longitud maxima propuesta. Una vez fijada la longitud, "<<endl; cout<<"genera la variable como una cadena de ceros y unos. "<<endl<<endl; cout<<"El metodo-2 genera la variable como una cadena de simbolos"<<endl; cout<<"pertenecientes al conjunto {0,1,2} de longitud la longitud"<<endl; cout<<"maxima propuesta. Despues reordena esta cadena, colocando"<<endl; cout<<"todos los doses al final de la misma (pues representan el "<<endl; cout<<"simbolo vacio). Finalmente, calcula el numero de ceros y "<<endl; cout<<"unos que aparecen en la cadena y que corresponde a la "<<endl; cout<<"longitud real de la variable generada."<<endl<<endl; cout<<"Que metodo desea utilizar?"<<endl; cin>> Metodo; }//fin if }while(Metodo<1 || Metodo>2); do{ cout<<"Si no desea aplicar busqueda local pulse 0, "<<endl; cout<<"en otro caso pulse 1 o 2 dependiendo del tipo de busqueda"<<endl; cout<<"que desea utilizar."<<endl; cout<<"Si desea recibir informacion acerca de los metodos usados"<<endl; cout<<"pulse 3."<<endl; cin>> Busqueda; 188 if(Busqueda == 3) { cout<<"El tipo de busqueda_1 aplica la busqueda local clasica a "<<endl; cout<<"cada solucion hasta que no mejora y luego cambia la "<<endl; cout<<"longitud de las variables en una unidad. Si hay mejora"<<endl; cout<<"repite el proceso."<<endl; cout<<"Mientras que la busqueda_2 aplica a cada variable de una "<<endl; cout<<"solucion la busqueda local y el cambio de longitud. Si al"<<endl; cout<<"final hay mejora se repite el proceso."<<endl; cout<<"Que tipo de busqueda local desea aplicar?"<<endl; cin>> Busqueda; }//fin if }while(Busqueda<0 || Busqueda>2); }//fin Pedir_Datos void Lee_Prob(const string &problema, int &n_ec, int &m_var, vector<int> &l_ec, vector<vector<int> > &Sist) { ifstream fprob(problema.c_str(),ios::in); fprob>>n_ec; fprob>>m_var; l_ec.resize(2*n_ec); Sist.resize(2*n_ec); for(int i= 0; i< 2*n_ec; i++){ fprob>>l_ec[i]; Sist[i].resize(l_ec[i]); for(int j= 0; j< l_ec[i]; j++) fprob>>Sist[i][j]; }//fin for(i) }//fin Lee_Prob void Simplifica(const int &n_ec, vector<int> &l_ec, vector<vector<int> > &Sist) { int coincidencia, columnas, minimo; 189 for(int i= 0; i< 2*n_ec - 1; i+= 2) { coincidencia= 0; columnas= 0; minimo= min (l_ec[i],l_ec[i+1]); while(Sist[i][columnas]== Sist[i+1][columnas] && columnas< minimo) { coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i]-coincidencia; for(int j= 0; j< l_ec[i]; j++) Sist[i][j]= Sist[i][j+coincidencia]; Sist[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; for(int j= 0; j< l_ec[i+1]; j++) Sist[i+1][j]= Sist[i+1][j+coincidencia]; Sist[i+1].resize(l_ec[i+1]); }//fin if coincidencia= 0; columnas= 1; minimo= min (l_ec[i],l_ec[i+1]); while(columnas<= minimo && Sist[i][l_ec[i]-columnas] == Sist[i+1][l_ec[i+1]-columnas]) { coincidencia= coincidencia +1; columnas= columnas +1; }//fin while if(coincidencia!=0) { l_ec[i]= l_ec[i] -coincidencia; Sist[i].resize(l_ec[i]); l_ec[i+1]= l_ec[i+1]-coincidencia; Sist[i+1].resize(l_ec[i+1]); }//fin if }//fin for(i) }//fin Simplifica 190 void Genera_PobIni(vector<vector<vector<int> > > &PobIni, vector<vector<int> > &lReVsol, vector<vector<vector<int> > > &AuxPob, vector<vector<int> > &AuxlRsol, const int &TamPob, const int &m_var, const int &lMaxVsol, const int &Metodo) { int longitud; PobIni.resize(TamPob); lReVsol.resize(TamPob); AuxPob.resize(TamPob); //vector auxiliar AuxlRsol.resize(TamPob); //vector auxiliar for(int k= 0; k< TamPob; k++) { PobIni[k].resize(m_var); lReVsol[k].resize(m_var); AuxPob[k].resize(m_var); //vector auxiliar AuxlRsol[k].resize(m_var); //vector auxiliar for(int i= 0; i< m_var; i++) { PobIni[k][i].resize(lMaxVsol); if(Metodo == 1) { longitud= lMaxVsol +1; lReVsol[k][i]= rand() % longitud; for(int j= 0; j< lReVsol[k][i]; j++) PobIni[k][i][j]= rand() % 2; for(int j= lReVsol[k][i]; j< lMaxVsol; j++) PobIni[k][i][j]= 2; }//fin if(Metodo==1) if(Metodo == 2) { lReVsol[k][i]= lMaxVsol; for(int j= 0; j< lMaxVsol; j++) PobIni[k][i][j] = rand() % 3; 191 reordeno(lMaxVsol, PobIni[k][i],lReVsol[k][i]); }//fin if(Metodo==2) }//fin for(i) }//fin for(k) }//fin Genera_PobIni void reordeno(const int &l, vector<int> &v,int &lReVsol) { // Reordeno la variable y calculo la longitud real de la misma int columna; for(int j= 0; j< l-1; j++) if(v[j] == 2) { columna= j+1; while(v[columna] == 2 && columna< l-1) columna = columna +1; if(v[columna]!= 2) { v[j]= v[columna]; v[columna]= 2; }//fin if }//if(v[j] == 2) for(int j= 0; j< l; j++) if(v[j] == 2) lReVsol= lReVsol -1; }//fin reordeno void Evalua(const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, const vector<int> &lReVsol, const vector<vector<int> > &PobIni, vector<int> &AuxIzq, vector<int> &AuxDer, int &fitness, int &haysol,int &prueba) { int lAuxIzq, lAuxDer; int coincidencia, minimo; 192 prueba=prueba+1; coincidencia = 0; for(int i= 0; i<n_ec; i++) { lAuxIzq= l_ec[2*i]; lAuxDer= l_ec[2*i+1]; AuxIzq.resize(lAuxIzq); AuxDer.resize(lAuxDer); sustituyo(Sist[2*i], lAuxIzq, AuxIzq, lReVsol, PobIni); sustituyo(Sist[2*i+1], lAuxDer, AuxDer, lReVsol, PobIni); minimo = min(lAuxIzq,lAuxDer); for(int j= 0;j< minimo; j++) if(AuxIzq[j] != AuxDer[j]) coincidencia= coincidencia+1; coincidencia= coincidencia + abs(lAuxIzq-lAuxDer); }//fin for(i) fitness= coincidencia; if(fitness == 0) haysol= 1; }//fin Evaluacion void sustituyo(const vector<int> &Sist, int &lAux, vector<int> &Aux, const vector<int> &lVsol, const vector<vector<int> > &PobIni) { int auxiliar, variable; int j, l; auxiliar = 0; for(j= 0;j< lAux; j++) if(Sist[j+auxiliar]==0 || Sist[j+auxiliar]==1) Aux[j] = Sist[j+auxiliar]; else { variable = Sist[j+auxiliar] - 3; if(lVsol[variable]==0) 193 { lAux=lAux-1; Aux.resize(lAux); auxiliar=auxiliar+1; j=j-1; } else { lAux=lAux+lVsol[variable]-1; Aux.resize(lAux); for(l= 0; l< lVsol[variable]; l++) Aux[j+l] = PobIni[variable][l]; auxiliar = auxiliar - lVsol[variable] + 1; j=j+lVsol[variable]-1; } }//fin else }//fin sustituyo void Mejor_Sol(int &MejorFit, vector<int> &fitness, const int &TamPob, const int &m_var, vector<vector<int> > &AuxPob, const int &lMaxVsol, vector<vector<vector<int> > > &PobIni, vector<int> &AuxlRsol, vector<vector<int> > &lReVsol) { int minimo, variable; variable = 0; MejorFit = fitness[0]; for(int k= 1; k< TamPob; k++) { if(fitness[k]< MejorFit) { MejorFit= fitness[k]; variable= k; 194 }//fin if(fitness[k]< MejorFit) if(MejorFit == fitness[k]) { minimo= rand() % 2; if(minimo == 0) { MejorFit= fitness[k]; variable= k; }//fin if(minimo == 0) }//fin if(MejorFit == fitness[k]) }//fin for(k) for(int i= 0; i< m_var; i++) { AuxPob[i].resize(lMaxVsol); AuxlRsol[i]= lReVsol[variable][i]; for(int j= 0; j< lMaxVsol; j++) AuxPob[i][j]= PobIni[variable][i][j]; }//fin for(i) }//fin Mejor_Sol void Ruleta(int &Sumfit, const int &TamPob, vector<int> &fitness) { Sumfit = 0; for(int k= 0; k< TamPob; k++) Sumfit= Sumfit + fitness[k]; for(int k= 0; k< TamPob; k++) fitness[k]= Sumfit - fitness[k]; Sumfit = 0; for(int k= 0; k< TamPob; k++) Sumfit= Sumfit + fitness[k]; }//fin Ruleta void eligo_padre(int &TamPob, int &Padre, vector<int> &fitness) { int limInf, limSup, coinciden; coinciden= 0; 195 limInf = 0; limSup = fitness[0]; for(int i= 0; i<TamPob && coinciden == 0; i++) { if(limInf <= Padre && Padre < limSup) { Padre = i; coinciden = 1; } else { limInf = limInf + fitness[i]; limSup = limSup + fitness[i+1]; } } }//fin eligo_padre void Cruce(const int &m_var, const int &lMaxVsol, vector<vector<int> > &AuxPob, vector<int> &lReVsolIzq, vector<int> &lReVsolDer, vector<int> &AuxlRsol, vector<vector<int> > &PobIniIzq, vector<vector<int> > &PobIniDer) { int minimo, columna, longitud; for(int i= 0; i< m_var; i++) { AuxPob[i].resize(lMaxVsol); minimo= min(lReVsolIzq[i],lReVsolDer[i]); AuxlRsol[i]= minimo; for(int j= 0; j< minimo; j++) { columna= rand() % 2; 196 if(columna == 0) AuxPob[i][j]= PobIniIzq[i][j]; else AuxPob[i][j]= PobIniDer[i][j]; }//fin for(j) longitud= lMaxVsol - minimo; columna= longitud +1; columna= rand() % columna; if(lReVsolIzq[i]< lReVsolDer[i]) { for(int j= 0; j< columna; j++) { AuxPob[i][minimo+j]= PobIniDer[i][minimo+j]; if(AuxPob[i][minimo+j]!= 2) AuxlRsol[i]= AuxlRsol[i] +1; }//fin for(j) for(int j= columna; j< longitud; j++) AuxPob[i][minimo+j]= 2; }//fin if else { for(int j= 0; j< columna; j++) { AuxPob[i][minimo+j]= PobIniIzq[i][minimo+j]; if(AuxPob[i][minimo+j]!= 2) AuxlRsol[i] = AuxlRsol[i] +1; }//fin for(j) for(int j= columna; j< longitud; j++) AuxPob[i][minimo+j] = 2; }//fin else }//fin for(i) }//fin cruce void Mutacion(const int &m_var, const int &lMaxVsol, int &muto, vector<vector<int> > &AuxPob, vector<int> &AuxlRsol) { for(int i= 0; i< m_var; i++){ for(int j= 0; j< lMaxVsol; j++) 197 { muto= rand() % lMaxVsol; if(muto == 0) AuxPob[i][j]= rand() % 3; }//fin for (j) AuxlRsol[i]= lMaxVsol; reordeno(lMaxVsol, AuxPob[i],AuxlRsol[i]); }//fin for(i) }//fin Mutacion void Busqueda_loc_A(const int &m_var, vector<int> &lReVsol, const int &lMaxVsol, const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, vector<int> &AuxIzq, vector<int> &AuxDer, vector<vector<int> > &PobIni, int &fitness, int &haysol, int &cont_uno, int &cont_dos, int &prueba) { int mejoro, repito, MejorFit, columna, valor, vizq, vder; do{ cont_uno = cont_uno +1; repito = 0; do{ cont_dos = cont_dos +1; mejoro = 0; for(int i= 0; i< m_var && fitness != 0; i++) { for(int j= 0; j< lReVsol[i]; j++) { if(PobIni[i][j] == 1) PobIni[i][j] = 0; else PobIni[i][j] = 1; MejorFit = fitness; Evalua(n_ec, l_ec, Sist, lReVsol, PobIni, AuxIzq, AuxDer, fitness, haysol, prueba); 198 if(fitness >= MejorFit) { fitness = MejorFit; if(PobIni[i][j] == 1) PobIni[i][j] = 0; else PobIni[i][j] = 1; } else mejoro = 1; }//fin for(j) }//fin for(i) }while(mejoro == 1 && fitness != 0); for(int i= 0; i< m_var && fitness != 0; i++) { //Incremento la variable i_ésima columna = lReVsol[i]; if(columna < lMaxVsol) { MejorFit = fitness; PobIni[i][columna]= 0; lReVsol[i] = columna +1; Evalua(n_ec,l_ec,Sist,lReVsol,PobIni,AuxIzq,AuxDer,fitness,haysol, prueba); if(MejorFit <= fitness) { fitness = MejorFit; PobIni[i][columna] = 2; lReVsol[i] = columna; } else repito = 1; vder = PobIni[i][columna]; MejorFit = fitness; PobIni[i][columna]= 1; 199 lReVsol[i] = columna +1; Evalua(n_ec,l_ec,Sist,lReVsol,PobIni,AuxIzq,AuxDer,fitness, haysol,prueba); if(MejorFit <= fitness) { fitness = MejorFit; PobIni[i][columna] = vder; if(vder == 2) lReVsol[i] = columna; } else repito = 1; }//fin if(columna < lMaxVsol) //decremento la variable i_ésima if(columna > 0 && fitness != 0) { valor = PobIni[i][columna -1]; if(columna < lMaxVsol) vder = PobIni[i][columna]; PobIni[i][columna -1]= 2; if(columna < lMaxVsol) PobIni[i][columna]= 2; MejorFit = fitness; lReVsol[i] = columna -1; Evalua(n_ec,l_ec,Sist,lReVsol,PobIni,AuxIzq,AuxDer,fitness,haysol, prueba); if(MejorFit <= fitness) { fitness = MejorFit; PobIni[i][columna -1]= valor; if(columna < lMaxVsol) { PobIni[i][columna]= vder; if(vder == 2) lReVsol[i] = columna; else lReVsol[i] = columna +1; 200 } else lReVsol[i] = columna; }//fin if else repito = 1; }//fin if if(lReVsol[i] > 0 && columna > 1) }//fin for(i) }while(repito == 1 && fitness != 0); }//fin Busqueda_loc_A void Busqueda_loc_B(const int &m_var, vector<int> &lReVsol, const int &lMaxVsol, const int &n_ec, const vector<int> &l_ec, const vector<vector<int> > &Sist, vector<int> &AuxIzq, vector<int> &AuxDer, vector<vector<int> > &PobIni, int &fitness, int &haysol, int &cont_uno, int &prueba) { int repito, MejorFit, columna, valor, vizq, vder; do{ repito = 0; cont_uno = cont_uno +1; for(int i= 0; i< m_var && fitness != 0; i++) { for(int j= 0; j< lReVsol[i]; j++) { if(PobIni[i][j] == 1) PobIni[i][j] = 0; else PobIni[i][j] = 1; MejorFit = fitness; Evalua(n_ec, l_ec, Sist, lReVsol, PobIni, AuxIzq, AuxDer, fitness, haysol,prueba); if(fitness >= MejorFit) { fitness = MejorFit; 201 if(PobIni[i][j] == 1) PobIni[i][j] = 0; else PobIni[i][j] = 1; } else repito = 1; }//fin for(j) //Incremento la variable i_ésima columna = lReVsol[i]; if(columna < lMaxVsol) { MejorFit = fitness; PobIni[i][columna]= 0; lReVsol[i] = columna +1; Evalua(n_ec, l_ec, Sist, lReVsol, PobIni, AuxIzq, AuxDer, fitness, haysol,prueba); if(MejorFit <= fitness) { fitness = MejorFit; PobIni[i][columna] = 2; lReVsol[i] = columna; } else repito = 1; vder = PobIni[i][columna]; MejorFit = fitness; PobIni[i][columna]= 1; lReVsol[i] = columna +1; Evalua(n_ec, l_ec, Sist, lReVsol, PobIni, AuxIzq, AuxDer, fitness, haysol,prueba); if(MejorFit <= fitness) { fitness = MejorFit; PobIni[i][columna] = vder; if(vder == 2) lReVsol[i] = columna; 202 } else repito = 1; }//fin if(columna < lMaxVsol) //decremento la variable i_ésima if(columna > 0 && fitness != 0) { valor = PobIni[i][columna -1]; if(columna < lMaxVsol) vder = PobIni[i][columna]; PobIni[i][columna -1]= 2; if(columna < lMaxVsol) PobIni[i][columna]= 2; MejorFit = fitness; lReVsol[i] = columna -1; Evalua(n_ec, l_ec, Sist, lReVsol, PobIni, AuxIzq, AuxDer, fitness, haysol,prueba); if(MejorFit <= fitness) { fitness = MejorFit; PobIni[i][columna -1]= valor; if(columna < lMaxVsol) { PobIni[i][columna]= vder; if(vder == 2) lReVsol[i] = columna; else lReVsol[i] = columna +1; } else lReVsol[i] = columna; }//fin if else repito = 1; }//fin if if(lReVsol[i] > 0 && columna > 1) }//fin for(i) }while(repito == 1 && fitness != 0); }//fin Busqueda_loc_B 203 Bibliografı́a [1] Abdulrab, H.: Résolution d’equations sur les mots: étude et implementation LIPs de l’algoritme de Makanin. Ph. D. dissertation, Univ. Rouen, Rouen. 1987 [2] Alonso, C.L., Drubi, F., and Montaña, J.L.: An Evolutionary Algorithm for Solving Word Equation Systems. [3] Bäck, T., Eiben, A., and Vink, M.: A superior evolutionary algorithm for 3-SAT. In Saravanan, N., Waagen, D., and Eiben, A., editors, Proceedings of the Seventh Annual Conference on Evolutionary Programming. Lecture Notes in Computer Science, Volumen 1477, pages 125 − 136, Springer, Berlin, Germany. 1998 [4] Eiben, A., and van der Hauw, J.: Solving 3-SAT with adaptative genetic algorithms. In Proceedings of the Fourfth IEEE Conference on Evolutionary Computation, pages 81 − 86, IEEE Press, Piscataway, New Jersey. 1997 [5] Goldberg, D. E.: Genetic Algorithms in Search, Optimization & Machine Learning. Addison-Wesley Publishing Company, Inc. 1989 [6] Gottlieb, J., and Voss, N.: Improving the performance of evolutionary algorithms for the satisfiability problem by refening functions. In Eiben, A. et al., editors, Proceedings of the Fifth International Conference on Parallel Problem Solving from Nature. Lecture Notes in Computer Science, Volumen 1498, pages 755 − 764, Springer, Berlin, Germany. 1998 204 [7] Gottlieb, J., and Voss, N.: Representations, fitness functions and genetic operators for the satisfiability problem. In Hao, J.-K. et al., editors, Proceedings of Artificial Evolution. Lecture Notes in Computer Science, Volumen 1363, pages 55 − 68, Springer, Berlin, Germany. 1998 [8] Gottlieb, J., Marchiori, E., and Rossi, C.: Evolutionary Algorithms for the Satisfiability Problem. Massachusetts Institute of Technology. 2002 [9] Gutiérrez, C.: Solving Equations in Strings: On Makanin’s Algorithm. Wesleyan University, Middletown, CT 06459, U.S.A. [10] Hao, J.-K.: A clausal genetic representation and its evolutionary procedures for satisfiatibility problems. In Pearson, D., Steele, N., and Albrecht, R., editors, Proceedings of the International Conference on Artificial Neural Nets and Genetic Algorithms, pages 289 − 292, Springer, Vienna, Austria. 1995 [11] Hmelevski, J. I.: Equations in free semigroups. Trudy Mat. Inst. Steklov. 107, 1971 [12] Holland, J. H.: Adaptation in natural and artificial systems. Ann Arbor: The University of Michigan Press. 1975 [13] Jaffar, J.: Minimal and Complete Word Unification. Journal ACM, Vol. 37, No 1, January 1990, pages47 − 85 [14] Koscielski, A., and Pacholski, L.: Complexity of Makanin’s Algorithm. Journal of the ACM, Vol. 43, July 1996, pages670 − 684 [15] Lentin, J.: Equations in free monoids, in automata languages and programming. In Nivat, M., editors, North Holland, pages67 − 85. 1972 [16] Makanin, G.S.: The problem of solvability of equations in a free semigroup, Math. USSR Sbornik 32(1997) (2), 129 − 198 205 [17] Marchiori, E. and Rossi, C.: A flipping genetic algorithm for hard 3-SAT problems. In Banzhaf, W. et al., editors, Proceedings of Genetic and Evolutionary Computation Conference, pages 393 − 400, Morgan Kaufmann, San Francisco, California. 1999 [18] McAllester, D., selman, B., and Kautz, H.: Evidence for invariants in local search. In Proceedings of the National Conference on Artificial Intelligent, pages 321 − 326, AAAI Press, Menlo Park, California. 1997 [19] Pécuchet, J. P.: Equations avec constantes et algoritme de Makanin. These de doctorat, Laboratoire dı́nformatique, Rouen, 1981 [20] Plotkin, G. D.: Building in equational theories, Mach. pages 73 − 90, 1972 [21] Robson, J. M., and Diekert, V.: On quadratic word equations. [22] Rossi, C., Marchiori, E., and Kok, J.: An adaptive evolutionary algorithm for the satisfiability problem. In Carroll, J. et al., editors, Proceedings of ACM Symposium on Applied Computing, pages 463 − 469, ACM, New York. 2000 [23] Selman, B., Kautz, H., and Cohen, B.: Noise strategies for improving local search. In Proceedings of the Twelfth National Conference on Artificial Intelligent, pages 337 − 343, AAAI Press, Menlo Park, California. 1994 [24] Schulz, K. U.: Word Unification and Transformation of Generalized Equatons. Journal of Automated Reasoning 11, pages 149 − 184, 1993 [25] Siekmann, J.: A modification of Robinson’s unification procedure, M.Sc. Thesis. 1972 206