107
TRABAJO DE FIN DE GRADO ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA UNIVERSIDAD DE SEVILLA GRADO INGENIERÍA INFORMÁTICA – TECNOLOGÍAS INFORMÁTICAS EVOGENET: Paralelización de la Dinámica Evolutiva de Redes Génicas Realizado por PEDRO ANTONIO VARO HERRERO Dirigido por JOSÉ LUIS GUISADO LIZAR DEPARTAMENTO DE ARQUITECTURA Y TECNOLOGÍA DE COMPUTADORES ANTONIO CÓRDOBA ZURITA DEPARTAMENTO DE FÍSICA DE LA MATERIA CONDENSADA Sevilla, Julio de 2014

TRABAJO DE FIN DE GRADO - Universidad de Sevillaopera.eii.us.es/.../G2013-2014-8/Grupo8Memoria1.pdf1. Introducción 4 1.2. OBJETIVOS El objetivo de este trabajo es el desarrollo de

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

  • TRABAJO DE FIN DE GRADO

    ESCUELA TÉCNICA SUPERIOR DE INGENIERÍA INFORMÁTICA

    UNIVERSIDAD DE SEVILLA

    GRADO INGENIERÍA INFORMÁTICA – TECNOLOGÍAS INFORMÁTICAS

    EVOGENET: Paralelización de la Dinámica Evolutiva de Redes Génicas

    Realizado por

    PEDRO ANTONIO VARO HERRERO

    Dirigido por

    JOSÉ LUIS GUISADO LIZAR

    DEPARTAMENTO DE ARQUITECTURA Y TECNOLOGÍA DE

    COMPUTADORES

    ANTONIO CÓRDOBA ZURITA

    DEPARTAMENTO DE FÍSICA DE LA MATERIA CONDENSADA

    Sevilla, Julio de 2014

  • INDICE

    1. INTRODUCCIÓN ........................................................................................................................ 3

    1.1. MOTIVACIÓN .................................................................................................................... 3

    1.2. OBJETIVOS ........................................................................................................................ 4

    1.3. ESTRUCTURA DE LA MEMORIA ........................................................................................ 6

    2. Antecedentes ........................................................................................................................... 8

    2.1. BIOLOGÍA DE SISTEMAS Y REDES DE REGULACIÓN GÉNICA ............................................ 8

    2.1.1. INTRODUCCIÓN A LA BIOLOGÍA DE SISTEMAS ......................................................... 8

    2.1.2. REDES DE REGULACIÓN GÉNICA ............................................................................. 10

    2.1.3. MODELOS MATEMÁTICOS DE REDES GÉNICAS ...................................................... 13

    2.2. ARQUITECTURAS DE COMPUTADORES Y PARADIGMAS DE PROGRAMACIÓN PARALELA

    16

    2.2.1. ARQUITECTURAS DE COMPUTADORES .................................................................. 16

    2.2.2. PARADIGMAS DE PROGRAMACIÓN PARALELA ...................................................... 20

    2.3. ALGORITMO GENÉTICO .................................................................................................. 25

    2.3.1. COMPONENTES DE UN ALGORITMO GENÉTICO .................................................... 26

    2.3.2. PSEUDO-CÓDIGO .................................................................................................... 26

    2.3.3. MODELOS PARALELOS DE ALGORITMOS GENÉTICOS ............................................ 29

    2.4. PYTHON, INTRODUCCIÓN AL LENGUAJE Y FRAMEWORKS DE COMPUTACIÓN DE ALTAS

    PRESTACIONES ........................................................................................................................ 33

    2.4.1. PYTHON, INTRODUCCIÓN AL LENGUAJE ................................................................ 33

    2.4.2. ESTUDIO SOBRE PROGRAMACIÓN PARALELA EN EL LENGUAJE PYTHON ............. 41

    3. IMPLEMENTACIÓN DEL ALGORITMO GENÉTICO ................................................................... 48

    3.1. PROCESO SECUENCIAL ................................................................................................... 48

    3.2. PROCESO PARALELO ....................................................................................................... 56

    4. ESTUDIO DE PRUEBAS Y RESULTADOS ................................................................................... 59

    4.1. ESTUDIO DE ACELERACIÓN DE LA APLICACIÓN. ............................................................. 63

    4.2. ESTUDIO DE ESCALABILIDAD DE LA APLICACIÓN. .......................................................... 69

    4.3. ESTUDIO DE PARÁMETROS DE COMUNICACIÓN DEL ALGORITMO. .............................. 74

    5. ANÁLISIS DE LA GESTIÓN Y DESARROLLO DEL TRABAJO ........................................................ 78

    6. CONCLUSIONES ...................................................................................................................... 83

    7. TRABAJO FUTURO .................................................................................................................. 85

    8. BIBLIOGRAFÍA ......................................................................................................................... 86

    Anexo A: Código fuente. ............................................................................................................. 88

  • EVOGENET: Paralelización de la Dinámica Evolutiva de Redes Génicas

    2

  • 1. Introducción

    3

    1. INTRODUCCIÓN

    1.1. MOTIVACIÓN

    Mi gran interés por la investigación científica en los dos últimos años de carrera ha sido

    una de las principales motivaciones para la realización de este trabajo, una motivación

    y afán de superación por adquirir nuevos conocimientos y poder aportar en un futuro a

    la sociedad avances de la ciencia es una de mis metas.

    Esta motivación me ha llevado a descubrir y estudiar cantidad de problemas

    interesantes en el ámbito de la matemática, física y biología de los cuales desconocemos

    la solución a infinidad de ellos y además podrían tener tantas soluciones como posibles

    diferentes situaciones en la vida real. Asimismo la aplicación de la informática a estos

    problemas ayuda a intentar resolver cada uno de ellos.

    La biología y especialmente el estudio del comportamiento de las células, genes y ADN

    es uno de esos campos científicos que más me ha llamado la atención y en el cual tengo

    especial interés, además ser uno de los temas tratados en este trabajo.

    Por otra parte uno de los campos de la computación en el cual tengo gran interés es en

    el estudio y uso de arquitecturas alto rendimiento y técnicas de aceleración de la

    ejecución, que actualmente son de gran ayuda a la hora de aplicar algoritmos y resolver

    problemas complejos.

    De la unión de la biología y la informática nace la bioinformática, una rama que aplica

    técnicas de computación para intentar comprender los sistemas biológicos. Si por parte

    de la computación le sumamos el uso de arquitecturas de alto rendimiento y por parte

    de la biología ese interés por saber cómo funcionan los mecanismos básicos de los seres

    crea un gran interés en este ámbito.

    Por todo esto en el trabajo vamos a comenzar estudiando qué son las redes de

    regulación génicas como porciones del ADN que cumplen una función determinada y

    su estudio puede hacernos entender procesos celulares, la creación de nuevos genes o

    la evolución de algún órgano vital. De igual forma analizaremos su modelización

    matemática como redes booleanas introducidas por primera vez por Stuart Alan

    Kauffman, biólogo teórico estadounidense, en 1969.

    Ya que el ADN contiene tal cantidad de información en un espacio minúsculo intentar

    manejar fragmentos de ADN con procesos de computación secuencial se hace una ardua

    tarea, por lo que trataremos de usar arquitecturas de computación de altas prestaciones

    y técnicas de aceleración para hacer más liviano todo el proceso y así poder obtener

    resultados.

  • 1. Introducción

    4

    1.2. OBJETIVOS

    El objetivo de este trabajo es el desarrollo de una aplicación para determinar la dinámica

    evolutiva de redes de regulación génica, mediante la implementación en paralelo de un

    método basado en un algoritmo genético, introducido por D. Aguilar-Hidalgo, Mª C.

    Lemos Fernández, Antonio Córdoba Zurita [1] sobre una plataforma de computación de

    altas prestaciones.

    La implementación se ha llevado a cabo utilizando el lenguaje Python, ya que el uso de

    este lenguaje se ha elevado entre la comunidad científica, además de su potencial para

    desarrollar complejas tareas en pocas líneas de código obteniendo un rendimiento más

    que decente.

    Para su desarrollo en paralelo se ha utilizado uno de las bibliotecas basadas en el

    paradigma de programación paralela por paso por mensajes, MPI, llamada MPI4Py y

    por último para el desarrollo del algoritmo genético se ha utilizado una biblioteca

    especializada en computación y algoritmos evolutivos llamada DEAP.

    El desarrollo de las pruebas se llevará a cabo sobre un clúster de computación de altas

    prestaciones compuesto por:

    Un Servidor Dell PowerEdge C6100:

    3 placas base, cada una con:

    2 procesadores Intel Xeon E5620 (4 cores, 2.40 GHz), DDR3-1066 MHz.

    24 GB de memoria (6×4 GB RDIMM 1333 MHz).

    4 discos duros de 600 GB SAS 6 Gbps 15k 3.5’’ Hot Plug.

    4 discos duros de 300 GB SAS 6 Gbps 15k 3.5’’ Hot Plug.

    Dos servidores Dell PowerEdge R410. Cada uno con:

    2 procesadores Intel Xeon E5620 (de 4 núcleos).

    24 GB de memoria (6×4 GB RDIMM 1333 MHz).

    500 GB de disco duro.

    Resumiendo, 10 procesadores Intel Xeon E5620 (4 núcleos, 2.40 GHz), organizados en 5

    nodos con 2 procesadores en cada nodo, 8 núcleos por nodo, en conjunto hacen un total

    de 40 núcleos, cada uno con una memoria cache de 12 Mb.

    Además los nodos se encuentran conectados a un conmutador gigabit Ethernet con

    cableado UTP RJ45.

  • 1. Introducción

    5

    El trabajo se desarrollará cumpliendo estos objetivos:

    A. Una descripción de la biología de sistemas y el ámbito de las redes de regulación

    génica.

    B. Un estudio de los algoritmos genéticos y sus modelos paralelos para su ejecución

    en sistemas de computación de altas prestaciones.

    C. Se hará un estudio del arte sobre las tecnologías de programación paralela en el

    lenguaje Python, para hacernos una idea de en qué estado se encuentra.

    D. La implementación del modelo de las redes de regulación génicas con un

    algoritmo genético con el lenguaje de programación Python, se hará en

    secuencial y paralelo, a continuación su ejecución se llevará a cabo en un sistema

    de computación de altas prestaciones.

    E. Por último, una vez obtenidos los resultados haremos un estudio de las mejoras

    que nos ofrece la computación de altas prestaciones y la paralelización del

    algoritmo.

  • 1. Introducción

    6

    1.3. ESTRUCTURA DE LA MEMORIA

    Sección 2: Antecedentes.

    Donde estudiaremos algunos temas de relevancia, necesarios para el desarrollo

    del trabajo.

    o 2.1: Biología de Sistemas y Redes de Regulación Génica

    Se describirá la rama de Biología de Sistemas, algunos conceptos

    biológicos sobre genética, más en concreto las redes de regulación génica

    o GRN, y el modelo matemático usado, redes booleanas. Aparte de su

    interés en la biología y aplicaciones de la vida real.

    o 2.2: Arquitecturas de Computadores y Paradigmas de Programación

    Paralela.

    Se da una visión de las arquitecturas y paradigmas de programación que

    actualmente se usan para computación de altas prestaciones.

    o 2.3: Algoritmos genéticos y modelos de paralelización.

    Introduciremos algo de historia de estos algoritmos, explicaré los

    elementos y fases de las que consta, y por último los modelos de

    implementación de este algoritmo en paralelo.

    o 2.4: Python, Introducción al Lenguaje y Frameworks de Computación

    de Altas Prestaciones.

    Se dará una visión actual del uso del lenguaje Python en la comunidad

    científica e introduciremos el lenguaje de programación Python para

    aquel que no lo conozca se familiarice con él. A su vez se describirá tanto

    él estudio sobre programación paralela en dicho lenguaje, como el

    estudio del estado del arte de frameworks de computación paralela y

    computación evolutiva.

    Sección 3: Implementación del Algoritmo Genético.

    Donde se explicará la implementación en secuencial y en paralelo del modelo

    matemático usado y el correspondiente algoritmo genético aplicado al modelo.

    Sección 4: Estudio de Pruebas y Resultados.

    Se hará un estudio sobre tres cuestiones fundamentales a la hora de la

    paralelización como son la aceleración, su escalabilidad y algunos parámetros

    propios de comunicación entre procesos.

  • 1. Introducción

    7

    Sección 5: Análisis de la gestión y desarrollo del trabajo.

    Se mostrará la división del trabajo en subtareas y su organización en el tiempo.

    Sección 6: Conclusión.

    Se dará una valoración final sobre el trabajo y sus posibles utilidades.

    Sección 7: Trabajo Futuro.

    Donde se destacarán cuestiones por hacer y posibles mejoras.

    Sección 8: Bibliografía.

    Se citará toda la documentación, fuentes, literatura, y web de donde se ha

    recopilado toda la información.

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    8

    2. ANTECEDENTES

    2.1. BIOLOGÍA DE SISTEMAS Y REDES DE REGULACIÓN GÉNICA

    2.1.1. INTRODUCCIÓN A LA BIOLOGÍA DE SISTEMAS

    Los seres vivos tenemos un nivel de organización y complejidad de la materia

    sorprendente, y creemos que toda información que describe cómo somos, crecemos y

    hacemos algunas funciones vitales se encuentra codificadas en los genes.

    El descubrimiento de la estructura del ADN en 1958 por Watson y Crick fue un

    acontecimiento muy importante ya que a partir de ello se inició un gran interés en la

    biología molecular por comprender los detalles y fundamentos del comportamiento y

    evolución de los sistemas biológicos.

    Desde este hecho surge un nuevo enfoque de estudio aplicado a los sistemas biológicos,

    llamado Biología de Sistemas, que se centra en el estudio de las complejas interacciones

    entre los componentes de los sistemas biológicos.

    Para intentar comprender estas interacciones hoy en día gracias a los avances en los

    laboratorios que son capaces de generar inmensas cantidades de información detallada,

    tenemos a nuestro alcance grandes bancos de información acerca de los procesos

    biológicos y sus interacciones, Figura 1. Además, para comprenderlos necesitamos de

    principios matemáticos, físicos y químicos básicos que describan y den sentido a los

    comportamientos de los sistemas biológicos.

    FIGURA 1.

    Conocimiento

    Biología

    TecnologíaComputación

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    9

    Podemos diferenciar en cuatros pasos los necesarios para hacer un estudio de los

    procesos que contempla la biología de sistemas

    a) Tenemos que hacer un análisis de las estructuras del sistema biológico que

    queremos analizar, para así definir las relaciones entre genes y las interacciones

    entre proteínas.

    b) Una vez obtenidas las estructuras y definidas las relaciones, estas se estudian y

    obtenemos los diagramas de interacciones, tales como pueden ser redes

    booleanas, obteniendo las redes de regulación genética.

    c) Una vez obtenida la red de regulación génica, la estudiamos como si fuera un

    grafo/red y analizamos sus propiedades topológicas.

    d) Obtenemos un modelo de la red, que imita el comportamiento de ella y por el

    cuál analizamos, interpretamos y predecimos resultados experimentales.

    Los enlaces entre componentes moleculares de estas redes vienen dados por reacciones

    químicas regidas por reglas de la química básica, al mismo tiempo estos enlaces pueden

    establecer estados funcionales, es decir que realizan una función determinada y real,

    definidos por procesos físicos-químicos.

    Estas redes de enlaces podemos verlas como redes complejas por su alta organización,

    su inmenso tamaño y su enorme cantidad de posibles enlaces y diferentes estados, que

    debido a su componente biológico determinan la naturaleza de la interacción y su factor

    auto-organizativo de componentes para formar de manera instantánea una red de

    carácter funcional, con una función determinada.

    Las características o rasgos observados en la red, llamados funciones fenotípicas, los

    cuales podemos obtenerlos observando la forma en la que interactúan la multitud de

    enlaces químicos proporcionan a la red una topología no lineal. Esta característica no

    lineal de las redes bioquímicas hace que a medida que aumentan en tamaño, el número

    de posibles estados funcionales crece más rápido que el número de componentes de la

    red y además el número de características observadas en la red no guarda relación lineal

    con el número de genes de la propia red.

    Uno de los rasgos más importantes y esenciales de las redes biológicas y con el cual sin

    ello no sería de tal interés, es su robustez, es decir, cómo el propio sistema conserva sus

    propias funciones frente a diversas perturbaciones, adaptándose a cambios en el medio

    o anomalías internas.

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    10

    Resumiendo, la disciplina de la Biología de Sistemas estudia el comportamiento de estas

    redes biológicas, así como sus enlaces, sus propiedades topológicas y su

    comportamiento no lineal para intentar explicar el complejo funcionamiento que tiene

    la propia naturaleza así como sus funciones fenotípicas.

    2.1.2. REDES DE REGULACIÓN GÉNICA

    Si examinamos la célula internamente podemos decir que está constituida por

    diferentes dispositivos o membranas integrados los cuales internamente y entre ellos

    tienen una inmensidad de tipos de interacciones entre proteínas, donde cada una lleva

    a cabo una función muy específica con gran precisión.

    FIGURA 2.

    EXTRAÍDA DE: HTTP://LIFEANDEARTHSCIENCES.WIKISPACES.COM/FILE/VIEW/CELULA-EUCARIOTA.JPG

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    11

    Las constante interacción con diferentes tipos de señales, como cambios de

    temperatura, presión, señales moleculares de otras células, nutrientes… A las cuáles

    responde mediante la generación de la proteína adecuada.

    Podemos definir un gen como una región del ADN cuya secuencia codifica la información

    necesaria para la formación de una proteína.

    Por lo que llamamos Red de transcripción o Red de Regulación génica a la red que

    determina el motivo o razón por la que se produce cada proteína. Es una red regulada

    porque es el propio sistema el que decide cuándo y cómo se va a generar la proteína.

    Para representar estos estados de interacción con las diferentes señales que puede

    recibir una célula ya sea del exterior o del interior del organismo, las células utilizan

    diferentes proteínas, llamados Factores de Transcripción.

    Por consiguiente podemos decir que cuándo un gen por alguna razón, factores internos

    o externos, decide transcribirse, es decir, dar respuesta a través de una proteína. Esta

    transcripción se ve afectada por los factores de transcripción, los cuales pueden ser otras

    proteínas anteriormente producidas por otros genes, figura 3. Se ve afectado en forma

    de aceleración o disminución del ritmo de producción de la proteína, figura 3.1.

    Estos Factores de Transcripción o proteínas son capaces de unirse al ADN, figura 3, para

    regular el ritmo al que se produce una determinada proteína. [2] [3] [4]

    FIGURA 3.

    Unión de un factor de transcripción a una parte del ADN y unión con un gen

    Extraida de -- http://www.biounalm.com/2012/03/la-enciclopedia-de-los-factores-de.html

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    12

    FIGURA 3.1

    Expresión de varios factores de transcripción con algunos genes. Extraida de [4]

    A continuación vamos a describir el proceso de transcripción de un gen, es decir el

    proceso mediante el cual se transporta la información necesaria de la secuencia del ADN

    para generar la proteína adecuada:

    De él gen que se va a transcribir, la ARN polimerasa, ARNp, extrae el código del ADN

    que representa al gen. A continuación produce una molécula ARN mensajera, ARNm, a

    la cual le introduce el código del gen extraído. Este ARNm es traducido/convertido en

    una proteína, llamado producto genético.

    Los Factores de trascripción podemos decir que son una representación interna del

    medio.

    Definimos el ritmo de transcripción de genes como el número de moléculas producida

    de ARNm por unidad de tiempo. Este ritmo viene a estar controlado por una región

    llamada cis-reguladora, la cual es una región del ADN donde se computa/decide sobre

    la expresión de un gen, es decir, qué proteína es el resultado de ese gen.

    Estos factores de trascripción pueden modular el ritmo de trascripción de un conjunto

    de genes, es decir, modifican la probabilidad por unidad de tiempo mediante la unión

    entre regiones cis-reguladoras de que los genes produzcan una cierta proteína, figura

    3.1.

    Como hemos dicho los factores de transcripción afectan a la tasa/ritmo por el cual la

    ARNp inicia la transcripción del gen o la producción de la proteína, estos factores pueden

    actuar en el ritmo de dos formas:

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    13

    Activador, aumentando el ritmo de producción de proteínas

    Represor, disminuyendo el ritmo de producción de proteínas o desactivándolo

    sin producir alguna proteína.

    Así pues podemos definir como Red de Regulación Génica como el conjunto de

    interacciones entre factores transcripcionales y genes, en donde se describen todas las

    interacciones entre genes en la célula. Esta red de regulación génica podemos verla de

    la siguiente manera:

    Los nodos son genes y los enlaces son los factores de transcripción de un gen por la

    proteína producto de otro gen. Es decir, esta interacción es la unión de la proteína

    producto de un gen X, con otro gen promotor Y que va a transcribir y producir otra

    proteína.

    Así pues podemos una como Red de Regulación Transcripcional como el conjunto de

    interacciones entre Factores Transcripcionales y genes, en donde se describen todas las

    interacciones de transcripciones reguladas en la célula. Esta red de regulación

    transcripcional gobierna la expresión génica y podemos verla de la siguiente manera:

    Los nodos son genes y los enlaces son los factores de transcripción de un gen por la

    proteína producto de otro gen. Es decir, esta interacción es la unión de la proteína

    producto de un gen X, con otro gen promotor Y que se va a transcribir.

    2.1.3. MODELOS MATEMÁTICOS DE REDES GÉNICAS

    Para la modelización de estas redes podemos encontrar tres principales modelos que se adaptan a su forma y características:

    Redes Booleanas

    Redes en Ecuaciones diferenciales

    Método de Monte Carlo En este trabajo nos vamos a centrar en el modelo de Redes Booleanas. Las redes booleanas para tratar de modelizar las interacciones entre genes del ADN fueron introducidas por Stuart Alan Kauffman, biólogo teórico estadounidense, en 1969. En estas redes los nodos son los diferentes genes o proteína producida por los genes, los cuales pueden tener dos estados, activo o inactivo. A su vez los enlaces entre nodos de la red son los factores trancripcionales, es decir marcan el ritmo con los que el gen receptor del enlace produce dicha proteína, además como se ha mencionado anteriormente estos enlaces pueden tener carácter activador, favoreciendo al ritmo de producción de la proteína o represor disminuyendo el ritmo de producción.

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    14

    En este tipo de redes los estados de los nodos vendrán dados por una expresión booleana en la que intervienen el tipo y número de enlaces. Esta red modificará su estado transcurrido el tiempo marcado como intervalos discretos, pudiendo estar la red con un estado definido en el primer intervalo y en el segundo intervalo los nodos tener un estado distinto.

    FIGURA 5.

    Representación de una red génica donde los nodos son los genes y los enlaces el factor de

    transcripción. Los colores en los nodos representan, verde que el nodo está activado, rojo que

    está desactivado. En los enlaces el color verde representa un enlace activador y el rojo un enlace

    inhibidor. Extraida de [1]

  • 2. Antecedentes 2.1. Biología de Sistemas y Redes de Regulación Génica

    15

    FIGURA 5.1

    Representa el tamaño normal de una red génica, extraída de [3]

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    16

    2.2. ARQUITECTURAS DE COMPUTADORES Y PARADIGMAS DE

    PROGRAMACIÓN PARALELA

    Actualmente con los avances tecnológicos en laboratorios biológicos y centros de

    colisiones de partículas somos capaces de generar inmensos bancos de datos con

    información detallada acerca de los procesos y sistemas a los que los aplicamos.

    Una vez obtenida toda esa información tenemos que operar para obtener resultados y

    sacar conclusiones por lo que cada vez más, nos hace falta el uso de arquitecturas de

    altas prestaciones de cómputo y el uso de técnicas de aceleración de cálculo.

    Por ello las arquitecturas de los procesadores y las formas de hacer cálculos con ellos

    han ido evolucionando hasta los grandes centros de datos repletos de procesadores y

    gp-gpu conectadas entre ellos para poder hacer cálculos masivos en paralelo.

    2.2.1. ARQUITECTURAS DE COMPUTADORES

    A continuación vamos a recordar la clásica clasificación de procesadores según la

    magnitud del flujo de instrucciones que pueden ejecutar y sobre cuántos flujos de datos

    pueden operar a la vez, definida por Michael J. Flynn en 1972 y llamada Taxonomía de

    Flynn [2] . A continuación se ilustra en la Tabla 1.

    Flujos de Instrucciones

    Flujos de Datos

    Simple Múltiple

    Simples

    Single Instruction

    stream Single Data

    stream

    (SISD)

    Single Instruction stream

    Multiple Data streams

    (SIMD)

    Múltiple

    Multiple Instruction

    streams Simple Data

    stream

    (MISD)

    Multiple Instruction streams

    Multiple Data streams

    (MIMD)

    TABLA 1. TAXONOMÍA DE FLYNN, Clasificación de computadores según Flujo de Instrucciones y Flujo

    de datos capaces de ejecutar

    SISD, son los procesadores capaces de operar con un único flujo de instrucciones sobre

    sólo un único flujo de datos, podemos decir que son las antiguas arquitecturas

    secuenciales.

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    17

    MISD, este tipo de arquitectura no existe ni tiene sentido ya que aplicar varios flujos de

    instrucciones a un mismo flujo de datos a la vez para conseguir un objetivo no tiene

    sentido. Algunos autores consideran dentro de esta categoría a sistemas de control con

    varios computadores que se aplican de forma redundante sobre los mismos datos. Por

    ejemplo sistemas de aviónica.

    SIMD, son las arquitecturas capaces de operar un mismo flujo de instrucciones sobre

    más de un flujo de datos a la vez.

    FIGURA 6. ARQUITECTURA SIMD, Representación de un flujo de instrucciones

    Estas arquitecturas las podemos ver presentes en las actuales las tarjetas gráficas o

    unidades de procesamiento gráfico, GP-GPU, las cuales tiene un nivel de paralelismo

    muy alto, también llamado de grano fino.

    Las unidades de procesamiento gráfico tienen alrededor de un número de entre 512-

    2024 procesadores muy simples pero capaces de ejecutar cada uno ellos el mismo flujo

    de instrucciones sobre un conjunto de flujos de datos.

    MIMD, esta arquitectura está formada por varios procesadores no sincronizados

    capaces de ejecutar varios flujos de instrucciones sobre múltiples flujos de datos.

    Seguidamente otra clasificación que podemos hacer es según la distribución de

    memoria. La cual se subdivide en dos grupos según cómo esté organizada físicamente

    su memoria en el espacio, podemos llamarlos de memoria centralizada y de memoria

    distribuida. Al mismo tiempo podemos hacer otra división según cómo se reparte

    lógicamente el espacio de memoria, podemos llamarlos de espacio de memoria

    compartida y de espacio de memoria separado. A continuación se ilustra en la tabla 2.

    Disposición Lógica

    Disposición Física

    Espacio de memoria compartido

    Espacio de memoria separado

    Memoria centralizada UMA ----

    Memoria Distribuida NUMA MPM

    TABLA 2, Clasificación de computadores según la disposición de la memoria.

    Inst.1

    Data1 Data2 Data3

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    18

    UMA son las siglas de “Uniform Memory Access” o Acceso Uniforme a Memoria, la

    mayoría de ellos son multinúcleos, con un número de procesadores menor o igual que

    32(treinta y dos), entre ellos comparten una memoria caché con una latencia uniforme,

    al mismo tiempo cada núcleo tiene su propio espacio de memoria cache. El principal

    cuello de botella de ellos lo tenemos en el acceso a la memoria principal. Podemos decir

    que son los procesadores de uso cotidiano. A continuación se ilustra en la figura 7.

    FIGURA 7, Ejemplo de arquitectura de un computador tipo UMA, extraída de [2].

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    19

    NUMA son las siglas de “Non Uniform Memory Acces” o Acceso No Uniforme a

    Memoria. Es una arquitectura donde hay varios procesadores conectados entre sí

    mediante una red en donde cada uno de ellos comparten el mismo espacio de memoria

    aún que estén en físicamente separados. Por lo que el acceso a esa memoria compartida

    dependerá de en qué parte de la memoria se encuentre el dato y donde se encuentre la

    memoria situada,

    FIGURA 8, Ejemplo de arquitectura de un computador tipo NUMA, extraída de [2].

    MPM son las siglas de “Message Passing Machine” o Maquina de Paso de Mensajes, son

    sistemas en donde cada máquina tiene su sistema operativo independiente y los cuales

    están conectado mediante una red de comunicación, por fibra óptica o tecnología

    Ethernet. Entre ellos se comunican a través del envío de mensajes.

    FIGURA 9, Ejemplo de arquitectura de un computador tipo MPM, extraída de [2] .

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    20

    2.2.2. PARADIGMAS DE PROGRAMACIÓN PARALELA

    Dadas las arquitecturas anteriores podemos clasificarlos en cuatro grupos según el

    paradigma de programación paralela que utilicen, es decir, el tipo de tecnología

    software de programación paralele que usen:

    Por manejo de Hilos.

    Por paso de mensajes.

    Hibrida: Manejo de Hilos y paso de mensaje.

    GPU-Computing

    Por manejo de hilos:

    Normalmente usado en arquitecturas de memoria compartida, ya que un conjunto de

    hilos comparten el mismo espacio físico/lógico de memoria y su intercambio de

    información, si es que lo hubiese, es directo y no impone mucha latencia en la

    comunicación.

    Alguna tecnología para hacer uso de este tipo de programación paralela es OpenMp [3],

    la cual ofrece un completo y cómodo framework para trabajar con hilos.

    FIGURA 10, Esquema de las diferentes funciones de la tecnología OpenMP, extraída de [3]

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    21

    Por paso de mensajes:

    Usada en arquitecturas de memoria y sistemas distribuidos para compartir la

    información entre procesos, procesadores y nodos. Como su propio nombre indica se

    basa en el envío de información mediante mensajes. Este paradigma ya implica un

    mayor coste de tiempo a la hora de intercambiar información, ya que si aplicamos esto

    a un clúster dependiendo de cómo estén conectados los nodos y la propia tecnología de

    la red, el tiempo de comunicación entre nodos será mayor que si comunicamos

    diferentes procesos en un mismo nodo.

    Para hacer uso de este tipo de programación paralela hay estándares como MPI que nos

    facilitan un framework para el paso de mensajes.

    Hibrido:

    Es la unión de hacer uso de computación paralela con paso de mensajes y a su vez con

    control de hilos, pudiendo así ocasionar paralelismo de grano fino y grano grueso a la

    vez.

    Esto se puede combinar perfectamente haciendo uso de tecnologías como OpenMp con

    la memoria compartida y el control de hilos y MPI para el paso de mensajes en la

    compartición de información entre procesos, procesadores y en clúster.

    GPU-Computing:

    Es hacer uso de lo que conocemos como tarjetas gráficas y usarlas como procesadores

    de propósito general para hacer cálculos con ellas. Estas nos permiten un grado de

    paralelismo a nivel de datos y memoria compartida muy alto gracias a su arquitectura

    diseñada con otra filosofía distinta a la de un procesador común.

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    22

    FIGURA 11.

    Comparación de arquitectura de CPU vs GPU, extraída de [4]

    Como vemos en la figura 11 y 12, a grandes rasgos una de las principales diferencias es

    que un procesador común tiene un número “pequeño” de núcleos y con funcionalidades

    e instrucciones bastante complejas. Mientras que una GPU tiene un gran número de

    procesadores y a su vez un inmenso número de núcleos pero con funciones e

    instrucciones muy simples, tales como sumar, restar y multiplicar números.

    FIGURA 12.

    Comparación de arquitectura de CPU vs GPU, extraída de [4]

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    23

    Existen dos tecnologías actuales para hacer uso de estas arquitecturas, OpenCL y por

    parte de NVIDIA una tecnología o paradigma llamado CUDA, desde la cual vamos a ver

    la arquitectura de una GPU de la siguiente forma en la figura 13.

    FIGURA 13.

    Arquitectura de una GP-GPU según NVIDIA CUDA, extraída de [4].

    Esta define a un Grid (dispositivo gpu) como un conjunto de bloques, en donde cada uno

    tiene su propia memoria compartida, sus registros e hilos. Actualmente el máximo de

    hilos por bloque es de 512, lo que nos da un nivel de paralelismo muy alto. Por lo que el

    número total de hilos en paralelo posibles a ejecutar vendrá determinado por el número

    de bloques que tenga la arquitectura y el número de hilos que permita ejecutar cada

    bloque.

    Algunas consideraciones de rendimiento a la hora de utilizar este tipo de arquitectura

    pueden ser [4]:

    Enviar toda la información posible a la memoria de la GPU y lanzar cuantos

    más hilos mejor.

    Esto es para aprovechar todo el potencial de la GPU, ya que enviar

    información de la memoria del sistema a la memoria de la GPU tiene

    mucho coste en tiempo computacional.

  • 2. Antecedentes 2.2. Arquitecturas de Computadores y Paradigmas de Programación Paralela

    24

    Mantener el SIMD/SIMT dentro de cada bloque.

    Esto significa que en todos los hilos que se lanzan se ejecute el mismo

    flujo de instrucciones a la vez, ya que si un conjunto de hilos está

    sumando y otro restando, estas operaciones no las va a hacer en

    paralelo, primero sumará y luego restará.

    Usar memoria compartida de la GPU siempre que se pueda.

    Acceder "bien" a memoria global, datos contiguos.

    Con esto quiero decir que se guarden datos y se accedan a ellos en

    direcciones de memoria contiguas.

  • 2. Antecedentes 2.3. Algoritmo Genético

    25

    2.3. ALGORITMO GENÉTICO

    Debido a los avances en computación de altas prestaciones la gran cantidad de datos que generamos diariamente, hacen que la ejecución de algoritmos en estos sistemas para acelerar los cálculos y procesos sea actualmente un tema muy motivador, en nuestro caso vamos a centrarnos en algoritmos bio-inspirados. La biología y los procesos naturales han sido fuente de inspiración para diseñar modelos y algoritmos matemáticos para modelar, simular y resolver problemas. Entre ellos nos podemos encontrar con:

    Redes Neuronales Artificiales

    Algoritmos Genéticos

    Algoritmos de colonias de animales

    Computación con membranas, P-Sistemas.

    Entre todos ellos nos vamos a centrar en los algoritmos genéticos, los cuales están basados en el proceso natural de evolución de las especies, donde los individuos se reproducen, sufre modificaciones y son sometidos a una función de evaluación. Por último, en el proceso de evolución sobreviven los que mejor se adapten al medio o mejor valor de función de evaluación tengan, favoreciendo así el elitismo de la especie. A su vez tiene un gran componente estocástico. Ya que la aplicación de estos algoritmos suelen ser a problemas de optimización tiene sentido favorecer el elitismo, ya que queremos encontrar la mejor solución a nuestro problema.

  • 2. Antecedentes 2.3. Algoritmo Genético

    26

    2.3.1. COMPONENTES DE UN ALGORITMO GENÉTICO

    Los algoritmos genéticos están compuestos de varios componentes que hacen que tengan una terminología especial a la hora de hablar de ellos, a continuación pasamos a describir esta terminología.

    Genes, material genético básico o forma de representar nuestro problema a

    resolver.

    Individuos, secuencia de genes que codifica un estado/solución al problema.

    Población, conjunto de individuos o diferentes soluciones, esta va ir

    evolucionando según generaciones.

    Función de evaluación, o función objetico nos evalúa los individuos de cada

    población, obteniendo un valor donde distinguimos si la solución es lo bastante

    buena para nuestro problema. Este va a ser nuestro valor a optimizar.

    2.3.2. PSEUDO-CÓDIGO

    1 Crear Población inicial.

    2 Evaluar población inicial.

    3 Repetir hasta condición de parada

    3.1 Seleccionamos padres.

    3.2 Aplicamos operador Entrecruzamiento sobre la pareja de padres elegida.

    3.3 Aplicamos operador Mutación sobre toda la nueva población generada.

    3.4 Evaluamos los nuevos individuos.

    3.5 Seleccionamos individuos para la siguiente generación.

    A continuación vamos a describir cada paso del Pseudo-código y cómo podemos desarrollarlo, ya que dependiendo de nuestro problema a resolver y la forma de representarlo, existen diferentes métodos que se adaptarán mejor o peor a nuestro caso:

    Crear Población inicial:

    Inicializamos la población aleatoriamente o con algún criterio definido. Debemos generar individuos que sean soluciones válidas a nuestro problema, ya sean buenas o malas.

  • 2. Antecedentes 2.3. Algoritmo Genético

    27

    Evaluamos la población inicial: Con la función de evaluación o función objetivo definida evaluamos la población inicial creada, que dependerá de cada problema. Por ejemplo si nuestro objetivo es encontrar el mínimo de una función no derivable o muy difícil de derivar, nuestra función objetivo será la propia función, pero si estamos resolviendo un problema como el del Viajante de comercio (TSP), nuestra función objetivo deberá ser la distancia recorrida en la solución actual.

    Iniciamos un bucle con un criterio de parada:

    Iniciamos el bucle principal donde el criterio de parada puede ser desde un número de generaciones hasta conseguir llegar a un valor de la función de evaluación deseado. En este tipo de algoritmos si nuestra función objetivo tiene muchos mínimos locales la única forma de llegar a conseguir un mejor valor de evaluación es optando por un número de generaciones más grande.

    Seleccionamos padres:

    Hacemos una selección de individuos padres para comenzar el proceso de generación de nuevos individuos hijo y modificación de genes. Esta selección la podemos hacer desde aleatoria hasta con diferentes criterios de selección. Por selección de torneo, seleccionar los mejores a pares o por selección de ruleta, la selección del individuo será directamente proporcional a su valor de la función de evaluación.

    Operador Entrecruzamiento:

    Con la anterior selección de padres, mediante un proceso definido y una componente estocástica-, generamos nuevos individuos, que serán los hijos de estos. Este proceso lo haremos según nos convenga y según la representación de nuestros genes e individuos, por ejemplo marcar uno o varios pivotes aleatorios e intercambiar las partes.

    Operador Mutación:

    Los individuos o hijos anteriormente creados, le aplicamos una modificación en los genes, es decir alteramos el contenido de la solución mediante un proceso definido y una componente estocástica cómo una probabilidad de mutación.

    Evaluamos los nuevos individuos:

    A los nuevos individuos o, hijos creados, les aplicamos la función de evaluación para saber cuál es su valor o como de buenos son.

  • 2. Antecedentes 2.3. Algoritmo Genético

    28

    Seleccionamos individuos para la siguiente generación:

    Entre los padres e hijos creados, elegimos mediante un proceso definido un número de ellos para que sean los individuos padres de la siguiente generación, normalmente se pasan a cada generación los mejores o los que mejor valor de la función objetivo tenga favoreciendo así el elitismo. Finalmente cuando tengamos definida la condición de parada nos quedaremos con la última población de individuos y entre ellos con los que mejor valor de la función de evaluación tengan.

    A continuación en la figura 14 se muestra un diagrama de flujo del algoritmo genético.

    FIGURA 14.

    Esquema general de ejecución de un algoritmo Genético.

    Condición de parada

    Selección de padres

    Aplicamos operador crossover

    Aplicamos operador mutación

    Evaluamos la nueva población

    Seleccionamos individuos para

    siguiente generación

    Iniciamos población inicial

    Evaluamos población inicial

  • 2. Antecedentes 2.3. Algoritmo Genético

    29

    2.3.3. MODELOS PARALELOS DE ALGORITMOS GENÉTICOS

    Una vez hemos descrito cada una de las partes y terminología de un algoritmo genético, vamos a pasar a describir los actuales modelos paralelos para la implementación en sistemas de computación de altas prestaciones. Actualmente existen principalmente dos modelos usados, el modelo en Islas y el modelo celular. El modelo en Islas está diseñado para una mejor ejecución en sistemas de memoria distribuida, mientras que el modelo celular está pensado para sistemas de memoria compartida [5] [6]. A continuación vamos a describir cada uno.

    Modelo Celular A este modelo se le da este nombre por su semejanza con los autómatas celulares. Si consideramos nuestra población como una matriz, en donde cada posición es un individuo de la población, decimos que nuestros individuos tan sólo se pueden comunicar con sus individuos vecinos, Figura 15. Esto implica que el algoritmo al evolucionar genere zonas de evolución aisladas.

    FIGURA 15. Población celular, ejemplo de vecindad en el modelo celular de un algoritmo genético

    Este modelo se da más para una arquitectura de memoria compartida y en donde se puedan ejecutar varios hilos a la vez, ya que podríamos hacer las operaciones de entrecruzamiento, mutación y evaluación de los individuos en paralelo, pudiendo asignar a cada hilo un individuo de la población.

  • 2. Antecedentes 2.3. Algoritmo Genético

    30

    Modelo en Islas

    También llamado modelo distribuido o multi-población, tiene presente la idea de tener varias subpoblaciones que llevan a cabo un intercambio de individuos entre ellas. Cada población corresponde a una ejecución del algoritmo, cada una de ellas en una isla distinta como podemos observar en la Figura 16.

    FIGURA 16.

    Modelos en islas, ilustración del modelo en islas de un algoritmo genético con cinco islas

    En un clúster de N núcleos conectados entre sí podríamos ejecutar N instancias del algoritmo genético y compartir individuos entre ellas.

    Isla 1

    Isla 2

    Isla 3Isla 4

    Isla 5

  • 2. Antecedentes 2.3. Algoritmo Genético

    31

    Lo que nos lleva a determinar una serie de parámetros de los que dependerá nuestra ejecución.

    Número de generaciones para intercambiar información.

    Ya que este modelo contempla la idea del intercambio de individuos entre islas tenemos que definir y estudiar cada cuántas generaciones hacemos el intercambio de individuos entre islas.

    Número de individuos que intercambiamos entre islas.

    Otro parámetro a definir y estudiar sería cuantos individuos intercambiamos entre islas.

    Como elegimos a los individuos que enviamos.

    A su vez debemos definir cómo vamos a elegir a los individuos que enviamos proponiendo algunos criterios.

    Eligiendo a los mejores individuos con respecto a su función de evaluación.

    Eligiendo individuos aleatoriamente entre la población.

    Eligiendo por el método de la ruleta, donde la probabilidad de elegir a un

    individuo es directamente proporcional al valor de su función de evaluación.

    Que individuos reemplazamos por los que nos llegan.

    Al igual que anteriormente hemos definido que individuos vamos a enviar, tenemos que definir qué individuos vamos a reemplazar por los que nos llegan proponiendo algunos criterios.

    Reemplazar a los peores individuos con respecto a su función de evaluación.

    Reemplazar individuos aleatoriamente.

    A qué isla le enviamos nuestro individuo.

    También podemos pensar a qué isla enviar la información. Normalmente la islas se nombran por el número del proceso y su intercambio se hace con el proceso siguiente, enviando el último proceso al primero.

  • 2. Antecedentes 2.3. Algoritmo Genético

    32

    Cuántas islas ejecutamos

    Por último un parámetro a estudiar es el número de algoritmos genéticos, islas o procesos a ejecutar ya que de ello depende el nivel de paralelización y uso de la arquitectura distribuida.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    33

    2.4. PYTHON, INTRODUCCIÓN AL LENGUAJE Y FRAMEWORKS DE

    COMPUTACIÓN DE ALTAS PRESTACIONES

    En esta sección vamos a dar una visión global acerca del aumento del uso de este

    lenguaje en la comunidad científica, además haremos una pequeña introducción sobre

    los elementos básicos del lenguaje de programación y por último se hará un estudio del

    estado del arte de librerías de computación de altas prestaciones que utilicen Python.

    2.4.1. PYTHON, INTRODUCCIÓN AL LENGUAJE

    Python en es un lenguaje que fue creado a finales de los años ochenta por el científico

    de la computación holandés Guido van Rossum [7] donde su filosofía es hacer que el

    código sea fácilmente legible, por lo que es tabulado.

    Python agrupa tres paradigmas de programación:

    Programación orientada a objetos, en Python todo es un objeto.

    Programación declarativa o imperativa

    Programación funcional

    Además es un lenguaje interpretado, por lo que no hace falta compilarlo, tan sólo un

    intérprete de código, el intérprete de Python. Como colector de basura/memoria tiene

    conteo de referencias. Los datos son débilmente tipados y dinámicos, esto quiere decir

    que las variables pueden tomar distintos tipos de datos a lo largo de la ejecución. A su

    vez no tenemos que definir el tipo de datos de las variables, es responsabilidad del

    programador el tratar las variables correctamente según su uso.

    Si hacemos un import this en la consola de Python, nos saldrán unos principios sobre el

    desarrollo en Python descritos por el creador, llamados el Zen de Python.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    34

    “The Zen of Python, by Tim Peters

    Beautiful is better than ugly.

    Explicit is better than implicit.

    Simple is better than complex.

    Complex is better than complicated.

    Flat is better than nested.

    Sparse is better than dense.

    Readability counts.

    Special cases aren't special enough to break the rules.

    Although practicality beats purity.

    Errors should never pass silently.

    Unless explicitly silenced.

    In the face of ambiguity, refuse the temptation to guess.

    There should be one-- and preferably only one --obvious way to do it.

    Although that way may not be obvious at first unless you're Dutch.

    Now is better than never.

    Although never is often better than *right* now.

    If the implementation is hard to explain, it's a bad idea.

    If the implementation is easy to explain, it may be a good idea.

    Namespaces are one honking great idea -- let's do more of those!”

    Para ver el uso del lenguaje primero vamos a describir las operaciones y tipos de datos

    más usados en python.

    Tipos de datos.

    Numéricos: int, long, float, complex.:

    Int y long Son los número enteros, int está comprendidos en ±2147483647 y

    long a partir de ese valor en adelante, aunque en python automáticamente pasa

    de un tipo de dato a otro si nos pasamos del valor máx/min de un int o viceversa.

    Float es el número decimal, representados con un punto.

    Complex son los números complejos definidos con su parte real e imaginaria.

    boolean, el clásico valor lógico True o False.

    str, un carácter o cadena de caracteres, se representan mediante ‘ ‘.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    35

    list:

    Como su propio nombre indica es una lista de elementos indexados, estos

    elementos pueden ser de distinto tipo y se representan mediante [ ], [1,2,3].

    set:

    Es lo que se conoce en matemáticas como un conjunto de elementos no

    repetidos sin orden, estos se representan mediante ( ), (1,2,3,4).

    tuple:

    Es una lista de elementos indexados pero inmutables, quiere decir que no

    podemos modificar su valor. Al igual que las listas puede contener elementos de

    varios tipos.

    dict:

    O diccionario es la clásica estructura de datos par clave-valor, donde la clave es

    única para cada valor, y los valores pueden ser elementos de cualquier tipo.

    None,

    Es un tipo de dato que dice que no tiene nada pero que existe la variable.

    Podemos utilizarlo para inicializar una variable o para tratar este tipo con alguna

    excepción.

    Todos estos datos son considerados objetos de primer orden por lo que, más que un

    tipo de dato, son objetos que tienen muchos métodos propios y comunes. Además la

    mayoría de ellos son iterables, esto quiere decir que podemos iterar sobre el objeto

    mismo y obtener sus valores directamente, los únicos que no lo son int, float y set.

    Una de las causas que ha hecho que se haya puesto tan de moda el lenguaje entre la

    comunidad científica es la simplicidad del código para hacer cosas que con otros

    lenguajes usados para el cálculo científico y de alto rendimiento como C, C++ y Fortran

    se hacen de forma más tediosa.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    36

    A continuación vamos a ver algunas de las operaciones que podemos hacer en Python.

    Dada su abundancia en funciones vamos a ver las más comunes a la hora de usar el

    lenguaje y algunas de especial interés para el desarrollo del trabajo.

    Todas las funciones descritas están basadas en la versión 2.7 de Python, por ser la que

    se ha utilizado en el trabajo. Actualmente la última versión estable es la 3.4 donde hay

    diferencias con algunas funciones [8].

    Todos los ejemplos que se describen a continuación para hacer una introducción al

    lenguaje han sido desarrollados por el autor del presente trabajo, además se

    encuentran subidos en el repositorio donde se ha desarrollado el trabajo [9].

    Operaciones con tipos númericos: int,long y float:

    1. """Podemos definir una variable con el constructor vacío""" 2. n=int() 3. f=float() 4. print n,f

    5. 0 0.0 6. 7. """O directamente dándole un valor tomando automáticamente el tipo oportuno se

    gún el valor.""" 8. n=100 9. f=9.6 10. print n,f 11. 100 9.6

    Operaciones con listas:

    1. """podemos definir una variable con el constructor vacío""" 2. l=list() 3. """O directamente le damos valores a la lista""" 4. l=[1,2,3,4,5,6,7,8] 5. print l 6. [1, 2, 3, 4, 5, 6, 7, 8] 7. 8. """Para acceder a un elemento de la lista podemos llamarlo por su índice""" 9. l[0] # -> 1 10. l[0] = 3 #Modificamos el valor de índice 0 a 3, l[0] -> 3 11. print l[:2] 12. [3, 2] 13. print l[2:6] 14. [3, 4, 5, 6] 15. print l[2:] 16. [3, 4, 5, 6, 7, 8] 17. 18. """Métodos""" 19. l.append(3) #Inserta al final de la lista el elemento 3 20. print l 21. [3, 2, 3, 4, 5, 6, 7, 8, 3] 22. l.extend([1,2,3]) #Añade la lista que se pasa como parámetro al final 23. print l 24. [3, 2, 3, 4, 5, 6, 7, 8, 3, 1, 2, 3] 25. print l.index(2) #Devuelve el índice del primer elemento de la lista que coinc

    ida con el del parámetro 26. 1

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    37

    27. l.reverse() #Da la vuelta a la lista 28. print l 29. [3, 2, 1, 3, 8, 7, 6, 5, 4, 3, 2, 3] 30. l.sort() #Ordena la lista según el valor de sus elementos 31. print l 32. [1, 2, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8]

    Operaciones con conjuntos:

    1. """podemos definir una variable con el constructor vacío""" 2. s=set() 3. """O con set(iterable)""" 4. s=set([1,2,3]) 5. print s 6. set([1, 2, 3]) 7. 8. s.add((1,2))#añadimos el elemento (1,2) 9. s.add((4,5))#añadimos el elemento (4,5) 10. print s 11. set([(1, 2), 1, 2, 3, (4, 5)]) 12. 13. s.remove(2) #eliminamos el elemento 2 14. print s 15. set([(1, 2), 1, 3, (4, 5)]) 16. 17. print len(s) #Número de elementos del conjunto/ cardinalidad 18. 4 19. print (1,2) in s #Pregunta si el elemento x está en s 20. True 21. print (4,5) not in s #Pregunta si el elemento x no está en s 22. False 23. 24. r=set([1,2,(1,2),9,10,(9,8)]) 25. print s.union(r) #Unión de s con r 26. set([(1, 2), 1, 2, 3, (4, 5), 9, (9, 8), 10]) 27. 28. print s.intersection(r)#Intersección de s con t 29. set([(1, 2), 1]) 30. 31. print s.difference(r) #Diferencia de s y t 32. set([(4, 5), 3]) 33. 34. v=s.copy()#Crea un copia del objeto conjunto s en la variable v. 35. print v 36. set([(1, 2), 1, 3, (4, 5)]) 37.

    Operaciones con tuplas:

    1. """podemos definir una variable con el constructor vacío""" 2. t=tuple() 3. """O directamente la damos valores""" 4. t=(8,4,5,6,[1,3],'b') 5. print t 6. (8, 4, 5, 6, [1, 3], 'b') 7. 8. """Las tuplas no tiene método append para añadir elementos

    o método extend para extender la tupla, son un tipo de objeto inmutable.No podemos modificar su contenido, tan sólo ver si un elemento está contenido, acceder por su indice e iterar sobre ellas"""

    9. print t[1] #Elemento del índice 1 10. 4

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    38

    11. print [1,3] in t 12. True 13. t[1]=5 14. TypeError: 'tuple' object does not support item assignment 15. 16. """Iterar sobre tuplas""" 17. for i in t: 18. print i 19. 8 | 4 | 5 | 6 | [1,3] | 'b'

    Diccionarios:

    1. """Es la estructura clave-valor, donde las claves son únicas, es decir, no puede haber claves repetidas

    2. y los valores pueden ser 1 o varios valores""" 3. 4. d=dict() 5. d={'jack': 4098, 'sape': 4139} 6. print d 7. {'sape': 4139, 'jack': 4098} 8. 9. f=dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) 10. print f 11. {'sape': 4139, 'jack': 4098, 'guido': 4127} 12. 13. d['jack'] #accedemos por la clave 14. #A continuación vamos a ver si una cadena de texto se encuentra como clave en

    algún diccionairo, por una parte vamos a pregunantar si ‘sape’ se encuentra como clave en el diccionario f ypor la otra parte vamos a hacer lo mismo pero con el diccionario d.

    15. ('sape' in f) or ( d.has_key('sape')) 16. f['hola']=320 #añadimos un nuevo elemento de clave 'hola' valor 320 17. print f 18. {'sape': 4139, 'jack': 4098, 'hola': 320, 'guido': 4127} 19. 20. print f.items() #Devuelve una lista con tuplas (clave,valor) 21. [('sape', 4139), ('jack', 4098), ('hola', 320), ('guido', 4127)] 22. print f.values() #Devuelve una lista con los valores 23. [4139, 4098, 320, 4127] 24.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    39

    A continuación vamos a ver algunas funciones que nos pueden resultar de ayuda en diferentes ocasiones y nos pueden ahorrar tiempo a la hora de desarrollar. Vamos a comenzar con algunas técnicas iterativas, todas ellas se pueden aplicar sobre cualquier objeto que sea iterable.

    1. l=[1,2,3,4,5] 2. """La función range(x), nos devuelve una lista de valores de tamaño x, desde 0

    hasta x-1 o range(x,y) entre x e y-1""" 3. d=range(1,6) 4. print l,d 5. [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] 6. 7. l=list() 8. for x in range(10): 9. l.append(x) 10. print l 11. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 12. 13. l=range(1,10) 14. """La función enumerate nos devuelve el elemento y el índice del elemento""" 15. for i,e in enumerate(l): 16. print i,e 17. 18. 0 1 | 1 2 | 2 3 | 3 4 | 4 5 | 5 6 | 6 7 | 7 8 | 8 9 19. 20. l=range(1,10) #[ 1, 2, 3, 4, 5, 6, 7, 8, 9] 21. s=range(11,20) #[11, 12, 13, 14, 15, 16, 17, 18, 19] 22. r=range(21,30) 23. """La función zip(s,l) nos devuelve elementos a pares,ternas... del mismo índi

    ce comenzando por el índice 1""" 24. for i,j in zip(l,s): 25. print i,j 26. 1 11 | 2 12 | 3 13 | 4 14 | 5 15 | 6 16 | 7 17 | 8 18 | 9 19 27. 28. for i,j,x in zip(l,s,r): 29. print i,j,x 30. 1 11 21 | 2 12 22 | 3 13 23 | 4 14 24 | 5 15 25 | 6 16 26 | 7 17 27 |

    8 18 28 | 9 19 29 31. 32. l=[(1,2),(4,5),(7,8)] 33. """Si nuestra lista está compuesta por pares de números podemos iterar

    directamente por los valores de las parejas de números 34. for x,y in l: 35. print x,y 36. 1,2 | 4,5 | 7,8 37. 38. """Iteramos sobre un diccionario""" 39. for i in d: #Itera sobre las claves 40. print i,d[i] 41. sape 4139 42. jack 4098 43.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    40

    Dado que la filosofía de Python es hacer lo más posible en una línea de código pero que sea elegante y fácilmente legible, para la generación de listas existen las listas por compresión:

    1. #Obtenemos una lista del 0 al 9 2. l=[x for x in range(10)] 3. print l 4. [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 5. 6. #Filtramos por los elementos pares del 0 al 9 7. l=[x for x in range(10) if x%2==0] 8. print l 9. [0, 2, 4, 6, 8] 10. 11. #Multiplicamos por 2 los elementos pares del 0 al 9 12. l=[x*2 for x in range(10) if x%2] 13. print l 14. [2, 6, 10, 14, 18] 15. 16. x=[3,6,8,2,5] 17. y=[1,4,6,5,1] 18. #Obtenemos todas la combinaciones de dos elementos de las listas x e y 19. l=[(i,j) for i in x for j in y ] 20. print l 21. [(3, 1), (3, 4), (3, 6), (3, 5), (3, 1), (6, 1), (6, 4), (6, 6), (6, 5), (6, 1), (

    8, 1), (8, 4), (8, 6), (8, 5), (8, 1), (2, 1), (2, 4), (2, 6), (2, 5), (2, 1), (5, 1), (5, 4), (5, 6), (5, 5), (5, 1)]

    22. 23. x=[3,6,8,2,5] 24. y=[1,4,6,5,1] 25. #Obtenemos pares de números por índice de las listas x e y 26. l=[(i,j) for i,j in zip(x,y)] 27. print l 28. [(3, 1), (6, 4), (8, 6), (2, 5), (5, 1)] 29. 30. matrix=[[3,6,8,2,5],[1,4,6,5,1],[1,4,5,6,7]] 31. l=[[row[k] for row in matrix] for k in range(5)] 32. print l 33. [[3, 1, 1], [6, 4, 4], [8, 6, 5], [2, 5, 6], [5, 1, 7]]

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    41

    2.4.2. ESTUDIO SOBRE PROGRAMACIÓN PARALELA EN EL LENGUAJE PYTHON

    Puesto que uno de los objetivos del trabajo final de grado es aplicar técnicas de

    programación paralela para optimizar los tiempos de ejecución, procedí a hacer un

    estudio sobre el estado de la cuestión en el lenguaje Python.

    Comenzando primero por investigar la paralelización en sistemas de memoria

    compartida, sabemos que para lenguajes de programación como Fortran, C y C++ existe

    el framework OpenMP [3], el cual nos facilita mucho la tarea a la hora de poder

    paralelizar bucles y en el manejo de hilos con variables compartidas y privadas.

    Una vez leída documentación acerca de este tema nos encontramos con dos librerías y

    un problema, las dos librería son propias del lenguaje Python y se llaman thread y

    multiprocessing.

    Módulo Thread

    La primera, thread, sirve para el manejo de hilos de Python y la segunda,

    multiprocessing, sirve para el manejo de procesos de Python, sabiendo sólo esto

    podemos decir que ya tenemos solucionado el tema acerca de la programación paralela

    en sistemas de memoria compartida, pero no es así.

    A continuación nos encontramos con un problema y es que de la forma en la que está

    construido Python no nos permite ejecutar más de un hilo a la vez en un intérprete de

    Python. La causa es un mecanismo llamado Global Interpreter Lock o GIL [10] mostrado

    en la Figura 17. Esto no significa que a la hora de crearnos hilos de trabajo en Python no

    tengamos que declararnos variables privadas y compartidas, ya que si tenemos varios

    hilos que van a modificar un mismo objeto Python debemos hacerlo.

    FIGURA 17. Acción del mecanismo GIL donde podemos ver cómo no es posible ejecutar más de un thread al

    mismo tiempo, extraída de [11].

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    42

    Esto significa que cada hilo tiene que esperar a que el GIL sea liberado para poder

    ejecutarse, por lo que no podemos ejecutar varios hilos a la vez como podemos ver en

    la imagen anterior.

    El tipo de bloqueo que usa es un semáforo binario construido desde un pthreads mutex

    y una variable de condición. La intención del GIL, según el creador del lenguaje, es

    proteger la memoria del intérprete.

    Este ha sido un tema muy discutido en la comunidad Python, ya que ha habido varias

    personas que han modificado el intérprete y han eliminado el GIL, pero el lenguaje se

    veía afectado en temas de estabilidad y rendimiento.

    Con lo que para temas de paralelización de hilos no nos serviría el módulo thread de

    Python, ni el lenguaje en sí.

    Entre C y Python, Cython.

    Pero esto no es todo lo que nos ofrece la comunidad Python, ya que su uso fue en

    aumento en comunidades matemáticas y científicas encontraron la necesidad de

    acelerar los cálculos. Por lo que se desarrolló un marco de trabajo entre el lenguaje C y

    Python llamado Cython [12]. Este nos permite escribir código con una sintaxis parecida

    a Python y algunas características de C para luego compilarlo como si fuera código en C

    y poder utilizarlo desde el intérprete de Python.

    Cython nos permite llamadas o importaciones de librerías propias de C, como por

    ejemplo OpenMP [3]. Al mismo tiempo nos deja a elección del usuario decidir si llevar o

    no un control de hilos de ejecución en paralelo con un simple noGIL= True en las partes

    del código donde queramos que el GIL no intervenga en la ejecución de hilos, pudiendo

    utilizar las funciones propias de OpenMP. A continuación tan sólo nos haría falta

    compilar nuestro código y hacer uso de él como si de un módulo de Python se tratase,

    pero en el fondo lo tenemos compilado cómo si fuera C.

    Muchas de los frameworks de Python dedicados al uso científico como son: Numpy,

    SciPy, Matplotlib, Pandas, IPython, Sympy [13] y Sage [14] entre los más importantes;

    tienen las funciones más complejas de ejecutar computacionalmente hablando

    desarrolladas en Cython para acelerar dichos cálculos.

    Tan sólo nos quedaría escribir un módulo con la sintaxis correspondiente en C, tratar los

    hilos como queramos y con la librería Cython [13], compilarlo para que el intérprete de

    Python lo entendiese.

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    43

    Un ejemplo de código en Cython que multiplica un vector por un escalar:

    1. from cython.parallel import * 2. def func(np.ndarray[double] x, double alpha): 3. cdef Py_ssize_t 4. with nogil: 5. for i in prange(x.shape[0]): 6. x[i] = alpha * x[i]

    Módulo Multiprocessing.

    El otro módulo que nos queda es el multiprocessing, el cual nos permite ejecutar varios

    procesos a la vez cada uno en diferentes intérpretes por lo que ya no estaríamos

    compartiendo memoria. La forma de compartir información entre ellos es mediante una

    cola de mensajes. Además la forma de hacerlo en Python es muy sencillo, con lo cual

    tenemos una forma de ejecutar procesos en paralelo e intercambiar mensajes entre

    ellos, aunque esta manera no sea tan eficiente como la compartición de memoria.

    Esto nos recuerda a la forma de intercambio de información en sistemas de memoria

    distribuida usando MPI.

    A esto se nos suma otro problema, y es que en el tipo de arquitectura en la que vamos

    a lanzar la aplicación, un cluster con varios nodos, la comunicación entre procesos de

    diferentes máquinas con el módulo multiprocessing no la podemos hacer. Por lo que

    por último fui a investigar qué tecnologías de paso de mensajes, MPI, podemos

    encontrar en el lenguaje Python.

    De entre todas las que encontré, me centré en cuatro, por ser las que mejor

    documentadas estaban, mejores comentarios de la comunidad de desarrollo tenían y en

    un estudio de un alumno de master de la Universidad de Oslo: WENJING LIN – A

    comparison of existing python modules of MPI [15].

    PyPar

    Proyecto de la Universidad Nacional de Australia.

    https://code.google.com/p/pypar/

    pyMPI

    Proyecto hecho por investigadores del

    Lawrence Livermore National Laboratory , California

    http://pympi.sourceforge.net/index.html

    https://code.google.com/p/pypar/https://code.google.com/p/pypar/http://pympi.sourceforge.net/index.htmlhttp://pympi.sourceforge.net/index.html

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    44

    MPI4Py

    Proyecto de Lisandro Dalcin, basado en MPI-1/2

    Implementa la mayoría de funciones de MPI

    https://bitbucket.org/mpi4py/mpi4py/

    SciPy.MPI:

    Proyecto de código libre, muy valorado en la comunidad python por su

    uso matemático, científico e ingenieril.

    http://scipy.org/

    Puedo decir que cada una de ellas son wrappers, es decir hacen llamadas a las funciones originales de C de los estándares de MPI definidos, por lo que para el uso de ellos con Python nos hará falta tener instalada alguna versión de MPI en C. En mi caso he utilizado OpenMPI 1.5.8.

    PyPar MPI4Py pyMPIP SciPy.MPI

    MPI_Send

    MPI_Recv

    MPI_Sendrecv

    MPI_Isend

    MPI_Irecv

    MPI_Bcast

    MPI_Reduce

    MPI_Allreduce

    MPI_Gather

    MPI_Allgather

    MPI_Scatter

    MPI_Alltoall

    TABLA 3. [14] FUNCIONES IMPLEMENTADAS DE LOS DIFERENTES MÓDULOS DE MPI EN PYTHON [15]

    https://bitbucket.org/mpi4py/mpi4py/http://scipy.org/

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    45

    C PyPar MPI4Py pyMPIP SciPy.MPI

    Latency 8 25 14 133 23

    Bandwith 967.004 898.949 944.475 150.901 508.972

    TABLA 4. [14]

    LATENCIA Y BANDO DE ANCHA DE LOS DIFRENTES MÓDULOS MPI PARA PYTHON [15]

    Como podemos ver en las tablas 3 y 4, de entre todas ellas MPI4Py es la que tiene

    implementadas la mayoría de funciones y la que mejor latencia y ancho de banda nos

    da después del uso de MPI con el lenguaje C. A continuación en la figura 18 se visualizan

    algunas de las funciones de comunicación del estándar MPI.

    FIGURA 18.

    Visualización de algunas funciones de MPI, extraída de [16].

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    46

    A continuación vamos a describir cómo usar MPI4Py, ya que sabiendo los estándares de

    MPI el uso de él en Python es directo, e incluso más fácil.

    A diferencia de MPI en C, MPI4Py está oriento al envío de objetos Python en mensaje,

    esto quiere decir que nos permitirá enviar cualquier objeto, sin especificar el tipo de

    dato, ni el tamaño y nosotros seremos los encargados de tratarlo según la situación.

    Para empezar, tenemos que importar los módulos que vayamos a utilizar. En nuestro

    caso necesitamos el módulo MPI del framework MPI4Py, y a continuación

    inicializaremos el comunicador principal y guardaremos el id/rango del proceso y el

    número total de procesos ejecutados.

    Estos ejemplos están obtenidos de la documentación de MPI4PY [17].

    1. from mpi4py import MPI 2. 3. """Iniciamos el comunicador""" 4. COMM=MPI.COMM_WORLD 5. 6. """Obtenemos el númerto total de procesos""" 7. SIZE=COMM.Get_size() #Get number of proceses 8. 9. """Obtenemos el id del propio processo""" 10. RANK=COMM.Get_rank() #Get id of own process

    Y a partir de ahora utilizaremos las funciones de MPI según nuestro objetivo, todas ellas las llamaremos desde el comunicador MPI.COMM_WORLD. Algunas de las funciones más comunes a utilizar son: send(), rcv(), bcast(), scatter(), gather(), allgather(), alltoall(), barrier. En la documentación tenemos muchas más. A continuación algunos ejemplos de uso:

    Ejemplo de envío/recepción de mensaje entre dos procesos:

    1. if rank == 0: 2. data = {'a': 7, 'b': 3.14} 3. """Enviamos al proceso de id 1, el objeto data""" 4. comm.send(data, dest=1, tag=11) 5. elif rank == 1: 6. """Recibimos los datos del proceso id 0""" 7. data = comm.recv(source=0, tag=11)

  • 2. Antecedentes 2.4. Python, Introducción al Lenguaje y Frameworks de Computación de Altas Prestaciones

    47

    Ejemplo de mensaje de broadcast:

    1. if rank == 0: 2. data = {'key1' : [7, 2.72, 2+3j], 3. 'key2' : ( 'abc', 'xyz')} 4. else: 5. data = None 6. """Automáticamente si es el proceso id 0 envia el mensaje de broadcast con la

    variable data a todos los procesos en ejecución y automáticamente el resto de procesos reciben el mensaje en la variable data"""

    7. data = comm.bcast(data, root=0)

    Ejemplo de mensaje Scatter:

    1. if rank == 0: 2. data = [(i+1)**2 for i in range(size)] 3. else: 4. data = None 5. """Automáticamente si es el proceso id 0 envía el mensaje con el elemento de

    indice 0 al proceso 1, indice 1 al proceso 1… y cada proceso recibe su mensaje en la variable data"""

    6. data = comm.scatter(data, root=0) 7. assert data == (rank+1)**2

  • 3. Implementación del Algoritmo Genético

    48

    3. IMPLEMENTACIÓN DEL ALGORITMO GENÉTICO

    Otro de los objetivo de este trabajo es el desarrollo de una aplicación para determinar

    la dinámica evolutiva de redes de regulación genética mediante la implementación en

    paralelo, sobre una plataforma de computación de altas prestaciones, de un método

    basado en un algoritmo genético, introducido por D. Aguilar-Hidalgo et. al. [1]. A

    continuación vamos a describir como se ha llevado a cabo esta implementación

    Para poder simular esta evolución de redes de regulación genética se ha implementado

    en el lenguaje Python el algoritmo genético, en el que tenemos un estado inicial de la

    red y un estado final, y queremos encontrar qué conjunto de reglas hacen pasar del

    estado inicial al final y además si se producen nuevos enlaces entre nodos. A

    continuación se describe más detalladamente la modelización del problema y las partes

    que componen el algoritmo genético.

    3.1. PROCESO SECUENCIAL

    Como se ha explicado anteriormente en la sección 2.1 una red de regulación génica la

    podemos expresar como un grafo o matriz booleana, donde sus nodos pueden tener

    valores 1 ó 0 e indican si el nodo está activado o inactivo y sus enlaces pueden tener

    valores -1, 0, 1 e indican la relación del link entre dos nodos cómo: -1 si es un enlace

    represor, 0 si no hay enlace y 1 si es un enlace activador. Veamos un ejemplo, donde las

    flechas rojas son enlaces inhibidores y las flechas verdes enlaces activadores.

    (

    0 0 1 1 0−1 0 0 0 10 −1 0 0 00 0 1 0 00 0 −1 0 0)

    FIGURA 19.

    Representación matricial de una red booleana, donde el color de los nodos rojo significa que se

    encuentra en estado desactivado y el color verde activado. Igualmente los enlaces de color verde

    tienen un carácter activador y los rojos inhibidores.

    En la figura 19 podemos ver que el estado de la red asociado a los diferentes nodos será:

    (0 0 0 1 1) donde cada posición corresponde a cada uno de los nodos.

    Como se describió en la sección 2.1 sobre modelos de redes génicas, el estado de los

    nodos en cada intervalo de tiempo vendrá dado por una expresión booleana la cual

    1 2

    3

    4 5

  • 3. Implementación del Algoritmo Genético

    49

    depende de los enlaces de entrada sobre el nodo. Los enlaces los encontramos de dos

    tipos, activador e inhibidores y los estados de los nodos pueden ser activos o inactivos,

    por lo que la expresión booleana podemos definirla por una serie de reglas que según el

    número y tipo de enlaces evolucionarán de una forma u otra. A continuación se

    describen cada una de las reglas de evolución de la red:

    1. Regla de la mayoría

    Cuando en un nodo actúan enlaces activadores y represores, el estado final del

    nodo lo determinará el tipo que tenga mayor número de enlaces.

    2. Regla del represor absoluto

    Cuando en un nodo basta con que haya un solo enlace represor, el nodo pasará

    a estar desactivado.

    3. Acción conjunta de dos activadores

    Cuando en un nodo basta con que actúen dos o más enlaces activadores, el nodo

    pasará a un estado activo.

    4. Acción conjunta de dos represores

    Cuando en un nodo basta con que actúen dos o más enlaces represores, el nodo

    pasará a un estado desactivado.

  • 3. Implementación del Algoritmo Genético

    50

    Ahora vamos a ver cómo describimos el problema en términos del algoritmo genético y

    como incluimos la aplicación de estas reglas. Anteriormente en la sección 2.2 ya

    explicamos los componentes de un algoritmo genético, así pues vamos a describir el

    objetivo de la aplicación del algoritmo genético a estas redes booleanas que modelan

    redes génicas.

    Este objetivo es encontrar qué reglas hacen pasar de un estado inicial A, a un estado

    final B a través de alguna de las soluciones que aporta el algoritmo genético, estado B*.

    Además si por casualidad en el estado B* aparecen enlaces nuevos entre nodos, ver si

    tiene sentido biológico. Figura 20.

    FIGURA 20.

    Representa el objetivo del algoritmo genético, donde a través de un estado inicial A y un estado

    final B se intenta encontrar un estado B* que haga pasar del estado inicial A al estado final B.

    1

    5

    3

    2

    4

    1

    5

    3 4

    2

    Estado Inicial A.

    1

    5

    3

    2

    4

    Estado Final B.

    Estado B*.

  • 3. Implementación del Algoritmo Genético

    51

    Genes, en nuestro problema tendremos dos tipos de genes, los valores de los enlaces

    con posibles valores entre-1, 0,1 y los números de las reglas con posibles valores entre

    1, 2, 3,4.

    Individuos, estos están compuesto de dos partes, la primera parte de enlaces y la

    segunda de reglas. La parte de enlaces no es más que nuestra matriz booleana que

    representa al grafo pero puesta en fila, una a continuación de otra. La parte de las reglas

    serán las reglas que determinan la evolución de este individuo de la red. Y la parte de

    los estados de los nodos de la red es una fila con los estados de cada nodo.

    FIGURA 21.

    Estructura de los individuos del algoritmo genético desarrollado, representan la red booleana.

    Compuestos de dos partes, la primera de enlaces y la segunda las reglas que determinan los

    estados de los nodos.

    Así pues nuestro individuo será una lista de tamaño 1056 elementos, ya que en nuestra

    red hay 32 nodos y necesitamos expresar la matriz de adyacencia de los nodos más la

    regla activada en cada nodo, por lo que 32 ∗ 32 = 1024; 1024 + 32 = 1056. En donde

    hasta la posición 1024 está definida la matriz de enlaces de nodos de la red genética y a

    partir de la posición 1024 están las diferentes reglas asociadas a cada nodo que deciden

    como va a evolucionar esta red.

    A su vez cada individuo tiene asociada otra lista de 32 elementos o número de nodos de

    la red que se corresponde con el estado de los nodos de la red, es decir si el nodo i se

    encuentra activado, 1, o desactivado, 0, debido a los enlaces y reglas,

    La función de evaluación la podemos definir en dos partes, la primera como las

    diferencias en enlaces que hay entre nuestro individuo actual y la red final. Y la segunda

    como las diferencias que hay en los estados de los nodos de la red actual y la red final.

    Además cada término queda multiplicado por un valor ƛ y ƛ-1 Quedando formulado de

    esta forma:

    𝑓 = 𝜆𝑑𝑖𝑠𝑡[𝐵, 𝐵∗]

    max (𝑑𝑖𝑠𝑡[𝐵, 𝐵∗])+ (1 − 𝜆)

    𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗]

    max (𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗])

    FUNCIÓN 1.

    Función de evaluación de los individuos del algoritmo genético, determina la bondad de cada

    solución encontrada.

  • 3. Implementación del Algoritmo Genético

    52

    Siendo B y B* el estado de los nodos de la red del individuo actual y la red final

    respectivamente, por lo que 𝑑𝑖𝑠𝑡[𝐵, 𝐵∗] es la diferencia de los estados de los nodos del

    individuo actual y la red final. Y max (𝑑𝑖𝑠𝑡[𝐵, 𝐵∗]) la máxima diferencia que puede haber

    entre el estado de los nodos del individuo actual y la red final.

    Por otra parte tenemos 𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗] que son las diferencias en enlaces que hay

    entre la red del individuo actual y la red final. Y max (𝑑𝑖𝑠𝑡[𝑙𝑖𝑛𝑘𝑠, 𝑙𝑖𝑛𝑘𝑠∗]) la máxima

    diferencia que puede haber entre enlaces de la red del individuo actual y la red final.

    La generación de la población inicial la hacemos con permutaciones sobre la red inicial,

    es decir dado el estado inicial A de la red, su lista de adyacencia, generamos una nueva

    lista con permutaciones aleatorias de los enlaces, con lo que obtenemos un nuevo

    individuo. Con esto conseguimos no añadir más número de enlaces en la red, pero sí

    modificar los enlaces entre los diferentes nodos. Para los siguientes individuos, hacemos

    permutaciones sobre el anteriormente creado en vez de sobre la red inicial A, con esto

    conseguimos la suficiente divergencia de soluciones, pero sin alejarnos demasiado del

    estado inicial A. Figura 22.

    FIGURA 22.

    Esquema de generación de la población inicial a partir del estado inicial A.

    Y la parte de reglas la generamos aleatoriamente para cada individuo, las reglas las

    definimos con los número del 1 al 4, por lo que obtenemos un número al azar para cada

    nodo.

    Estado

    Inicial A Permutamos

    enlaces de la

    red

    Nuevo individuo

    Añadimos a la población

    inicial

    Pérmutamos anterior

    individuo creado.

  • 3. Implementación del Algoritmo Genético

    53

    Por otra parte el estado de los nodos de la red para cada individuo generado, es decir si

    los nodos están activados o desactivados, lo obtenemos de la red inicial A. Podemos

    decir que todos los individuos de la población inicial van a tener el mismo estado de

    nodos que la estado inicial A.

    Además, la población inicial la generaremos con un tamaño de 100 individuos.

    El operador entrecruzamiento lo definimos eligiendo un pivote al azar en la parte de los

    enlaces e intercambiando las partes. Una vez se ha hecho el intercambio comprobamos

    que la conectividad de estos individuos, en términos de enlaces, se mantiene en el rango

    10%−+ de la red final y si está en el rango procedemos a la parte de reglas. Esta parte

    también viene marcada por un puntero en la parte de las reglas dado por expresión 2:

    𝐾 = 𝑖𝑛𝑡 (𝑙

    𝑁) + 1

    EXPRESIÓN 2.

    Expresión que nos calcula el punto de entrecruzamiento de dos individuos en la parte de los

    enlaces.

    Siendo K el puntero que marca la parte de reglas que se intercambian, l es el puntero

    usado anteriormente para la parte de los enlaces y N el número total de nodos de la

    red. A continuación lo mostramos en la Figura 23.

    FIGURA 23. OPERADOR ENTRECRUZAMIENTO

    Muestra cómo funciona el operador entrecruzamiento entre dos individuos.

    La elección de padres para el crossover la hacemos por el método de la ruleta, lo que

    quiere decir que la probabilidad de que un individuo sea elegido como padre es

    directamente proporcional a su valor de función de evaluación.

    Puntero 𝑙,

    parte de

    enlaces

    Puntero 𝑘,

    parte de las

    reglas

  • 3. Implementación del Algoritmo Genético

    54

    El operador mutación, lo definimos con dos probabilidades, una para la parte de enlaces

    𝜇𝑙 y otra para la de reglas 𝜇𝑟 . Iteramos sobre cada elemento del individuo y por cada

    elemento obtenemos un número aleatorio entre 0.0 y 1.0, si ha superado la probabilidad

    correspondiente mutamos ese valor a los otros restantes, siendo para los enlaces 1/2 la

    probabilidad para cada valor, ya que sólo quedan dos posibles valores de enlaces. Y para

    las reglas tenemos 1/3 para cada valor, ya que sólo quedan tres valores posibles de

    reglas. Por último si no hubiésemos superamos la probabilidad de mutación pasamos al

    siguiente elemento de la lista.

    Después de hacer un estudio y documentarnos [1] [18] [19] sobre las redes génicas en

    hemos elegido los siguientes valores para la probabilidad de mutación, ya que son los

    que mejor imitan el comportamiento de estas cómo: 𝜇𝑙 = 0.001 y s 𝜇𝑟 = 0.5 .

    A continuación pasamos a la parte de ejecución del algoritmo, donde vamos a poder dar

    como parámetros varias variables:

    Número de nodos de la red

    Probabilidad de mutación en links

    Probabilidad de mutación en reglas

    Lambda para función de evaluación

    Red inicial, enlaces y estados de los nodos

    Red final, enlaces y estados de los nodos

    Número de individuos de la población

    Número de generaciones a evolucionar

  • 3. Implementación del Algoritmo Genético

    55

    Vamos a ver en un diagrama de flujo, figura 24, cómo sería la ejecución del algoritmo

    añadiendo la aplicación de reglas en el algoritmo genético.

    FIGURA 24. ALGORITMO GENÉTICO IMPLEMENTADO

    Indica el diagrama de flujo del algoritmo genético desarrollado.

    3.1 Seleccionamos a padres

    3.2 Aplicamos el

    operador Crossover

    3.3 Aplicamos el

    operador Mutación

    3.4 Aplicamos las reglas definidas

    3.5 Evaluamos

    la población

    3.0 Repetimos el proceso N generaciones

    1.0 Generamos Población Inicial

    2.0 Evaluamos la población inicial

  • 3. Implementación del Algoritmo Genético

    56

    3.2. PROCESO PARALELO

    En la sección 2.4 ya describimos los posibles modelos paralelos de un algoritmo

    genético, recordándolo teníamos dos modelos. El modelo en Islas y el modelo celular,

    donde el primero está más orientado a un sistema de memoria distribuida, donde ca