• No se han encontrado resultados

GENERACIÓN DE MALLAS DE ELEMENTOS FINITOS EN PARALELO

N/A
N/A
Protected

Academic year: 2021

Share "GENERACIÓN DE MALLAS DE ELEMENTOS FINITOS EN PARALELO"

Copied!
61
0
0

Texto completo

(1)

UNIVERSIDAD DE MENDOZA

FACULTAD DE INGENIERÍA

INGENIERÍA EN INFORMÁTICA

“GENERACIÓN DE MALLAS DE ELEMENTOS FINITOS EN 

PARALELO”

Autor:

Emiliano López

Asesores:

Mg. Ing. Juan José Ciarlante

Dr. Ing. Carlos García Garino

(2)

RESUMEN

PROGAM es un programa muy viejo, escrito en FORTRAN IV, que genera mallas  estructuradas de elementos finitos. Este trabajo consiste en el desarrollo de una nueva  versión   del  programa,  denominada  PROGAM­P,  diseñada   para  correr   en   un  cluster 

beowulf.  Como   primer   paso   se   realizó   una   nueva   implementación   del   PROGAM  en 

Fortran 90, que sirvió como punto de partida para programar la versión paralela, la cual  utiliza la biblioteca MPI para implementar el paralelismo. Utilizando 20 procesadores, el PROGAM­P generó una malla de elementos finitos 13  veces más rápido que el PROGAM, lo que equivale a una eficiencia del 65%. Los clusters beowulf representan una alternativa sumamente económica a la hora de  implementar una máquina paralela. Desde el punto de vista del rendimiento los resultados   obtenidos con el PROGAM­P son muy buenos. Sin embargo, antes de paralelizar un  determinado algoritmo, es necesario estudiar qué partes del mismo son concurrentes y  cuáles no, ya que en muchos casos un enfoque paralelo no es la mejor solución.

(3)

ÍNDICE GENERAL

I. INTRODUCCIÓN...4 II. MARCO TEÓRICO Y ANTECEDENTES...7 III. DESARROLLO DE INGENIERÍA...28 IV. RESULTADOS...40 V. CONCLUSIONES...47 VI. REFERENCIAS...49 VII. CÓDIGO FUENTE...51

(4)
(5)

INTRODUCCIÓN

La programación de algoritmos complejos con un enfoque secuencial, es cada vez  menos factible debido a problemas en la eficiencia de cómputo. El desarrollo de la  computación de alto rendimiento ha dado lugar a un crecimiento sostenido de la demanda  de  potencia   de   cálculo,   que   no   es   posible   satisfacer   con   arquitecturas   basadas   en  esquemas secuenciales. Frente a este problema existen dos alternativas. Una de ellas se basa en el uso de  computadoras con arquitecturas paralelas, que tienen por lo general un costo prohibitivo   para la mayoría de las organizaciones. La otra opción, mucho más económica, consiste  en implementar  clusters beowulf, los cuales están formados por un conjunto de PCs  interconectadas en red, y coordinadas por un software que permite hacerlas trabajar como  si se tratara de una sola máquina paralela.

Este   Trabajo   Final   trata   de   la   extensión   a   entornos   paralelos   del  Programa   de  Generación   Automática   de   Mallas   (PROGAM),   un   software   que   genera   mallas 

estructuradas   de   elementos   finitos.   El   método   de   elementos   finitos   (MEF)   es   un  procedimiento numérico para resolver ecuaciones diferenciales, ampliamente utilizado en  el campo de la física y la ingeniería.

El trabajo fue realizado en el Laboratorio de Producción Integrada por Computadora  (LAPIC),   ubicado   en   la   sede   de   la   carrera   Redes   y   Telecomunicaciones   del   ITU  (UNCuyo). Dicho laboratorio cuenta  con  un  cluster beowulf  para realizar trabajos de  investigación vinculados a la computación de alto rendimiento.

Objetivos

1. Realizar   una  nueva   implementación   del  PROGAM  en   Fortran  90,   ya   que  el  programa original está escrito en FORTRAN IV y sólo se dispone de una versión 

(6)

impresa del código fuente. 2. Realizar una implementación paralela del PROGAM para ser ejecutada en un  cluster beowulf, utilizando la biblioteca de paso de mensajes MPI. 3. Estudiar el rendimiento del programa paralelo poniendo especial  énfasis en la  comparación con la versión secuencial. 4. Dotar al LAPIC de un generador de mallas estructuradas de elementos finitos que  sea de utilidad para los trabajos de investigación que allí se realizan.

(7)
(8)

EL MÉTODO DE ELEMENTOS FINITOS

El método de elementos finitos (MEF) es un procedimiento numérico para resolver  ecuaciones diferenciales, ampliamente utilizado en el campo de la física y la ingeniería.

El   concepto   fundamental   del   MEF   es   que   cualquier   función   continua,   como  temperatura, presión, o desplazamiento, puede ser aproximada por un  modelo discreto  formado por   un conjunto de funciones continuas definidas sobre un número finito de  subdominios.

Construcción del modelo discreto

El modelo discreto se construye de la siguiente manera: 1. Se identifica un número finito de puntos en el dominio. Estos puntos son llamados  nodos. 2. El valor de la función continua en cada punto nodal debe ser determinado. 3. El dominio es dividido en un número finito de subdominios denominados elementos.  Estos elementos están conectados por los nodos y juntos aproximan la forma del  dominio. 4. La función continua es aproximada en cada elemento por un polinomio que se define  utilizando los valores de la función en los nodos que corresponden al elemento. Para  cada   elemento   se   define   un   polinomio   diferente,   pero   los   polinomios   se   deben   seleccionar de manera tal que se mantenga la continuidad a lo largo de las fronteras  del elemento.

El concepto fundamental quedará más claro usando como ejemplo la distribución de  temperatura en una barra, como se muestra en la Figura 1. La función continua es T(x) y  el dominio es el intervalo 0,L a lo largo del eje x. Se identifican cinco puntos en el dominio 

(9)

(Figura 2a), estos puntos son los nodos, y no es necesario que sean equidistantes. Se  podría haber definido más de cinco puntos, pero con estos es suficiente para ilustrar los  conceptos   básicos.   Se   calcula   en   cada   nodo   el   valor   de  T(x),   esto   se   muestra  gráficamente en la Figura 2b.

Figura 1: Distribución de temperatura en

una barra.

Figura 2: Definición de los puntos nodales.

La división del dominio en elementos se puede realizar de dos maneras. Podemos  limitar cada elemento a dos nodos, obteniendo cuatro elementos (Figura 3a), o podemos  dividir el dominio en dos elementos, cada uno con tres nodos (Figura 3b). El polinomio en 

(10)

cada   elemento   se   define   utilizando   los   valores   de  T(x)  en   los   puntos   nodales.   Si  subdividimos la región en cuatro elementos habrá dos nodos por elemento y la función  elemental   será   lineal.   La   aproximación   final   de  T(x)  consistirá   de   cuatro   funciones  continuas lineales, cada una definida sobre un elemento (Figura 4a).

Figura 3: División del dominio en elementos.

La división del dominio en dos elementos permite que los polinomios elementales sean  de segundo  grado. La aproximación final de  T(x)  en este caso serán dos funciones  continuas cuadráticas (Figura 4b).

(11)

Figura 4: Aproximación final de T(x). Generalmente la distribución de temperatura se desconoce, y se desea determinar los  valores de la función en ciertos puntos. El procedimiento es igual al que se describió  anteriormente pero con un paso adicional. Se define un conjunto de nodos y los valores  T1, T2, T3, ..., son ahora variables desconocidas. El dominio es dividido en elementos, y se   define en cada uno de ellos una ecuación de temperatura. Los valores nodales de T(x)  deben   ser   ajustados   de   manera   que   provean   la   mejor   aproximación   posible   a   la  distribución real. El ajuste se realiza minimizando alguna función asociada al problema  físico.   Cuando   se   consideran   problemas   de   transferencia   de   calor,   se   minimiza   un  funcional relacionado con la ecuación diferencial que gobierna el problema. El proceso de  minimización  produce  un sistema  de  ecuaciones algebraicas lineales que  puede  ser  resuelto para obtener los valores nodales de T(x).

(12)

Los elementos en un dominio bidimensional son funciones de x e y, y generalmente son  triángulos o cuadriláteros. La función elemental puede ser un plano (Figura 5), o una  superficie curva (Figura 6). El plano está asociado al mínimo número de nodos por  elemento, que es tres para el triángulo y cuatro para el cuadrilátero.

Figura 5: Elementos planos en un dominio

bidimensional.

La   función   elemental   puede   ser   una   superficie   curva   si   se   agregan   más   nodos,  además, esto permite que los elementos tengan fronteras curvas. La aproximación final  de la función continua   x , y será una colección de superficies, cada una definida  sobre un elemento utilizando los valores de   x , y en los puntos nodales.

Figura 6: La función elemental es una superficie

(13)

definir la función elemental es un aspecto importante del MEF. Esta propiedad permite  que   la   función   elemental   sea   definida   independientemente   de   la   posición   final   del  elemento   en   el   modelo   conectado,   e   independientemente   de   las   otras   funciones  elementales.

Generación de la malla de elementos finitos

La generación de la malla de elementos finitos constituye el punto 3 en el proceso de  construcción del modelo discreto, presentado al comienzo de este capítulo. El resto de los  puntos están fuera del alcance de este Trabajo Final, y sólo fueron descriptos para ubicar  al lector en un contexto adecuado. La malla de elementos finitos es el resultado de la división del dominio de una función  en subregiones o elementos. Para esto el ingeniero debe decidir el número, tamaño y  forma de los elementos que utilizará para modelar el cuerpo real. En las zonas en donde   la función a aproximar varía con rapidez (gradientes altos) se utilizan elementos pequeños  para lograr una buena aproximación, mientras que en las zonas donde la función es  relativamente constante, se puede incrementar el tamaño de los elementos para reducir el  esfuerzo computacional. La generación de la malla se completa con la numeración de los  nodos y los elementos.

Este   trabajo   se   ocupa   de   la   división   de   dominios   bidimensionales   en   elementos  triangulares o cuadrangulares lineales, es decir que los elementos a utilizar serán el  triángulo de tres nodos y el cuadrilátero de cuatro nodos. La división de cualquier dominio bidimensional debe comenzar con la división del  cuerpo en macroregiones triangulares o cuadrangulares, de acuerdo con la geometría, las  propiedades físicas del material y/o la carga aplicada. Estas regiones son luego divididas  en triángulos o cuadriláteros, siendo necesario en el primer caso dividir la región en  cuadriláteros para luego dividir cada cuadrilátero en dos triángulos. La Figura 7 muestra la 

(14)

división de una región cuadrangular en elementos triangulares.

Figura 7: División de una región cuadrangular en

(15)

COMPUTACIÓN PARALELA

A continuación se introducen los conceptos generales sobre procesamiento paralelo  en clusters tipo beowulf, y se describe la interfaz de comunicaciones MPI, el software que  permite el intercambio de mensajes entre los procesos en un ambiente multiprocesador. 

El cluster beowulf

Un  cluster  beowulf  está formado por un conjunto de PCs conectadas mediante una  LAN, con una infraestructura de software libre (GNU/Linux). El hardware utilizado es el  que se encuentra disponible en el mercado común, y puede ser tan simple como dos  máquinas   compartiendo   un   sistema   de   archivos   o   tan   complejo   como   1024   nodos  conectados mediante una red de alta velocidad. El rendimiento es proporcional a la   cantidad de nodos instalados, pudiéndose agregar o quitar nodos según las necesidades.

Desde el punto de vista arquitectónico un cluster beowulf es un sistema de memoria  distribuida MIMD  (multiple­instruction, multiple­data). Este sistema consta de múltiples  procesadores independientes, es decir que no comparten la memoria principal sino que  cada   uno   tiene   su   propia   memoria,   y   colaboran   entre   sí   intercambiando   mensajes  mediante una red de comunicaciones. Así, cada procesador ejecuta su propia secuencia  de instrucciones y accede a sus propios datos, almacenados ambos en su memoria local.  Eventualmente   los   procesadores   pueden   intercambiar   datos   mediante   el   envío   de  mensajes a través de la red.

Paralelismo y concurrencia

Se dice que dos partes de un programa se procesan en forma concurrente, si cada  una de ellas puede ser ejecutada en forma independiente de la otra. En cambio decimos 

(16)

que se ejecutan en paralelo, si ambas partes son procesadas al mismo tiempo, siendo  necesario contar para esto con dos procesadores. Podemos concluir entonces, en que la  concurrencia es una característica lógica, en tanto el paralelismo es una característica  física. Por lo tanto para que una tarea pueda ser ejecutada en forma paralela debe ser   concurrente. Esto implica que a la hora de plantear la solución a un problema, debemos determinar  qué partes del mismo son concurrentes y cuáles no, lo cual hace que no todos los  programas sean “paralelizables”.

La interfaz de pasaje de mensajes MPI

El método más usado en la programación de sistemas de memoria distribuida MIMD  es   el   pasaje   de   mensajes,   o   alguna   variante   de   éste.   Básicamente,   los   procesos  coordinan sus actividades enviando y recibiendo mensajes.

MPI es un estándar desarrollado por el Message Passing Interface Forum (MPIF), con  el objetivo de crear una interfaz de programación para el intercambio de mensajes entre  procesos. Dado que MPI es un estándar abierto, existen diversas implementaciones, para  distintos   lenguajes   y   arquitecturas,   siendo   las   más   difundidas   MPICH   y   LAM/MPI.  Además, un mismo programa puede ser compilado tanto para ejecutarse en un  cluster  beowulf,  como en una computadora con una arquitectura paralela nativa, sin hacerle  modificaciones. La biblioteca MPI es una API que oculta al programador los detalles del manejo de las   comunicaciones entre procesos, ofreciendo de este modo un entorno transparente para  desarrollar programas paralelos. Las dos funciones más elementales que provee son  MPI_SEND   y   MPI_RECV,   para   enviar   y   recibir   un   mensaje   respectivamente;   a  continuación se muestra la sintaxis de estas subrutinas en Fortran:

(17)

MPI_SEND (buffer, count, dataype, destination, tag, communicator, ierr) MPI_RECV (buffer, count, datatype, source, tag,  communicator, status, ierr)

Todos los parámetros son enteros, a excepción de buffer, que puede ser de cualquier  tipo, dependiendo del tipo de datos a transmitir. El parámetro  ierr  es un argumento de  salida que cumple la función de valor de retorno, indicando si hubo o no error durante la  ejecución de la rutina. MPI le asigna a cada proceso en ejecución un identificador único denominado rank,  quedando los procesos numerados entre 0 y p­1, donde p es el número total de procesos.  Cada proceso obtiene su rank como sigue: CALL MPI_COMM_RANK(MPI_COMM_WORLD, my_rank, ierr) De esta manera el rank del proceso quedará guardado en la varialble my_rank.  Para ilustrar el uso de estas funciones, supongamos que el proceso 0 quiere enviar al  proceso 1 el vector x, compuesto por diez números reales. Para esto debe llamar a la  subrutina MPI_SEND como sigue: CALL MPI_SEND (x, 10, MPI_REAL, 1, 0,  MPI_COMM_WORLD, ierr) Para recibir el mensaje, el proceso 1 debe llamar a MPI_RECV de la siguiente manera: CALL MPI_RECV (x, 10, MPI_REAL, 0, 0, MPI_COMM_WORLD, status, ierr) Nótese que la instrucciones ejecutadas por el proceso 0 (MPI_SEND) serán diferentes  de las ejecutadas por el proceso 1 (MPI_RECV). Sin embargo esto no significa que los  programas deban ser diferentes. Simplemente podemos incluir la siguiente estructura  condicional en nuestro código: CALL MPI_COMM_RANK (MPI_COMM_WORLD, my_rank, ierr)

(18)

IF (my_rank == 0) THEN CALL MPI_SEND (x, 10, MPI_REAL, 1, 0, & MPI_COMM_WORLD, ierr) ELSE IF (my_rank == 1) THEN CALL MPI_RECV (x, 10, MPI_REAL, 0, 0, & MPI_COMM_WORLD, status, ierr) END IF Esta forma de programar sistemas MIMD es llamada SPMD (single­program multiple­ data). El efecto de ejecutar programas diferentes se logra mediante el uso de estructuras  condicionales en el código. Este es el enfoque más común utilizado en la programación  de sistemas MIMD. Al comenzar la ejecución en un esquema del tipo SPMD, se envía una copia del  programa a cada uno de los procesadores. Así, cada copia del programa constituye un  proceso. Por ejemplo, para ejecutar un programa utilizando la biblioteca MPICH2, se  utiliza un comando desde la consola del sistema operativo denominado mpiexec, al cual  se le dan como argumentos el nombre del ejecutable y la cantidad de procesos que se  desea crear. 

(19)

PROGRAMA DE GENERACIÓN AUTOMÁTICA DE MALLAS 

(PROGAM)

El Programa de Generación Automática de Mallas (PROGAM), escrito en Fortran IV  por Carlos García Garino y C. Ortiz Andino, se basa en el programa GRID publicado por  L.J. Segerlind, y genera datos de elementos para distintos programas de elementos  finitos.   Su   función   consiste   en   dividir   un   dominio   bidimensional   en   elementos  cuadrangulares y/o triangulares lineales.

Características del programa

El   programa   es   capaz   de   modelar   dominios   bidimensionales   complejos   que   se  obtienen como la unión de regiones más sencillas llamadas macroelementos,   cuyas  fronteras responden a curvas de segundo grado (Figura 8).

Dentro   de   cada   una   de   estas   regiones   se   generan,   mediante   interpolación  isoparamétrica, los nodos y elementos de la malla. Los elementos generados pueden ser  triángulos o cuadriláteros lineales y se contempla la posibilidad de generar cuadriláteros  en algunas regiones y triángulos en otras.

Cada región se modela mediante un cuadrilátero de 8 nodos, que puede utilizarse  como   rectángulo   (Figura   9),   cuadrilátero   general   o   triángulo   (Figura   10).   Para   este  cuadrilátero se define un sistema de coordenadas naturales  ,  que tiene su origen  en el centro del mismo, y donde −11 y −11 . Una región triangular se  modela alineando dos lados del cuadrilátero general.

(20)

 Figura 9: Región cuadrangular.

.

Figura 8: Un dominio triangular modelado como la unión de

(21)

Figura 10: Región triangular.

Para dividir una región en elementos, el PROGAM realiza el siguiente proceso:

Se parte de la región  normalizada definida por el sistema de coordenadas  , .  Para esta región se generan n filas por m columnas de nodos equidistantes; n y m son  datos suministrados por el usuario.

Cada   nodo   generado   en   el   sistema  ,  se   mapea   en   el   sistema    x , y   empleando la siguiente interpolación isoparamétrica: x=

i=1 8 iXi y=

i =1 8 iYi1=−0.25∗1−∗1− ∗ 1 2=0.5∗1−2∗1− 3=0.25∗1∗1− ∗− −1 4=0.5∗1∗1−2 5=0.25∗1∗1 ∗ −1

(22)

6=0.5∗1−2∗1 

7=0.25∗1−∗1∗ −−1

8=0.5∗1−∗1−2

Los vectores X e Y contienen las coordenadas en el plano  x , y de los ochos  nodos que definen la región, comenzando por la esquina inferior izquierda y procediendo  en sentido antihorario. Una vez obtenidas las coordenada (x, y) de los nodos, se le debe asignar a cada uno  un número entero para su identificación. Es importante aclarar que esta numeración es  global, es decir que abarca a todas las regiones. Además las regiones vecinas comparten  los nodos ubicados sobre la frontera común, por lo tanto se debe realizar un control para  determinar si los nodos ubicados en las fronteras, han sido numerados previamente,  durante el procesamiento de la región vecina. Si el control es positivo, los nodos sobre la  frontera   analizada   conservan   la   numeración   existente.   A   continuación   se   numeran  secuencialmente los nodos de la región, comenzando por el ubicado en las coordenadas =−1 y =1 , y procediendo de izquierda a derecha y de arriba hacia abajo. Los  nodos previamente numerados se saltean. Finalmente se definen los elementos de la región como vectores de tres o cuatro  enteros, según sean triángulos o cuadriláteros. Cada vector contiene los números de los  nodos que forman el elemento.

(23)

  Problema de ejemplo

Para ilustrar el procedimiento presentado en la sección anterior, se usará el dominio  triangular de la figura 8, el cual se subdivide en una región cuadrilátera y dos triángulos. Las coordenadas (x, y) de los nodos que definen las regiones son datos de entrada  suministrados por el usuario. La numeración de los mismos es arbitraria. Los nodos 4 y 6  se desplazan hacia el nodo 5 para obtener elementos más pequeños cerca de la esquina.

Para   la   región  2   se  elige  una   subdivisión  de   cinco  filas  y  cinco  columnas.   Esta  selección fija el número de filas en la región 3 en cinco y el número de columnas en la  región 1 en cinco, ya que las regiones vecinas comparten los nodos ubicados sobre la  frontera.

Elementos más grandes se obtienen en las regiones 1 y 3 colocando sólo tres filas de  nodos en la región 1, y tres columnas en la región 3. La región 3 será subdividida en  elementos   cuadrangulares,   mientras   que   para   las   restantes   se   utilizarán   elementos  triangulares.

A   continuación   se   detalla   el   procedimiento   realizado   para   la   generación   de   los  elementos en la región 3, definida por los nodos 1, 2, 3, 18, 13, 15, 16 y 17; para las otras   dos regiones se sigue un proceso similar: 

1) Se generan en la región normalizada definida por el sistema  ,  cinco filas por  tres columnas de nodos (Figura 11).

(24)

Figura 11: Generación de

los nodos en la región normalizada. 2) Se calculan las coordenadas (x, y) de los nodos, aplicando para cada uno las fórmulas  de la interpolación isoparamétrica (Figura 12).  Figura 12: Resultado de la interpolación isoparamétrica. 3) Se numeran los nodos comenzando por el extremo superior izquierdo y procediendo  de izquierda a derecha y de arriba hacia abajo. Los nodos ubicados en los bordes  comunes son numerados por la región que tenga el menor número, es decir que los  nodos ubicados en el borde derecho fueron numerados durante el procesamiento de la  región 2, y deben saltearse (Figura 13).

(25)

Figura 13: Numeración de los nodos en la región 3. La

secuencia comienza por el número 36 porque los menores a éste fueron asignados en las regiones 1 y 2. Los valores de la frontera derecha fueron asignados durante el procesamiento de la región 2.

4) Finalmente se obtienen los elementos (Figura 14).

Figura 14: Elementos generados dentro de la

(26)

Una vez procesadas las tres regiones, se combinan los resultados para formar la malla  de elementos finitos (Figura 15). 

Figura 15: Malla de elementos finitos generada con el

(27)
(28)

LA NUEVA IMPLEMENTACIÓN DEL PROGAM

La versión original del PROGAM sólo está disponible en papel, junto con una gran  cantidad   de   documentación   acerca   de   su   funcionamiento.   Por   alguna   razón   que  desconozco, la versión digital del programa se perdió, y para poder comenzar con este  trabajo necesitaba un PROGAM ejecutable. Una posible solución a este problema consistía en escanear el código fuente y pasarlo  por un OCR. Esto hubiera tomado mucho tiempo, ya que como el OCR no es perfecto, se   tendría que revisar todo el código para corregir los errores. Además, el código no es  estructurado y contiene muchas instrucciones  goto, lo que dificulta su comprensión y  estudio.

Decidí entonces   realizar  una  nueva  implementación   en  Fortran  90,  analizando  la  documentación técnica y el código fuente existente. El nuevo programa me serviría como  base para desarrollar el PROGAM paralelo, y para comparar el rendimiento de la solución  paralela con el de la secuencial. A continuación describiré los aspectos principales de esta nueva implementación.

La estructura de datos region_t

Toda la información referida a una determinada región del dominio está contenida en  una estructura de datos, del tipo region_t. la cual está definida en el archivo region.f90.

(29)

TYPE región_t     ! Número de región     INTEGER      :: num     ! Número de filas y columnas de nodos     INTEGER      :: n, m     ! Tipo de elemento (3=triangular, 4=cuadrangular)     INTEGER      :: elem_type     ! Datos de conectividad     INTEGER      :: vecinas(4)     ! Los 8 ocho nodos que definen la región     REAL       :: def_nodes(8,2)     ! Coordenadas (x,y) de los nodos     REAL, ALLOCATABLE    :: node_coords(:,:,:)     ! Números de los nodos     INTEGER, ALLOCATABLE :: node_nums(:,:)     ! Cada elemento es un vector de 3 o 4 enteros     INTEGER, ALLOCATABLE :: elements(:,:) END TYPE region_t Número de región: Las regiones se numeran en forma secuencial a partir de 1. Número de filas y columnas: Definen la cantidad de nodos en la región y determinan la  cantidad de elementos:  (n­1)(m­1)  elementos cuadrangulares o  2(n­1)(m­1)  elementos  triangulares. Tipo de elemento:  Los únicos elementos admitidos son el triángulo de tres nodos  (elem_type = 3) y el cuadrado de 4 nodos (elem_type = 4). Datos de conectividad: El vector vecinas contiene los números de las regiones vecinas  inferior, derecha, superior e izquierda respectivamente. Un cero en cualquiera de sus  elementos indica que no hay ninguna región en la frontera correspondiente.

Definición de la región:  La matriz  def_nodes  contiene las coordenadas  (x, y)  de los  ocho nodos que definen la región, comenzando por el del extremo inferior izquierdo y  siguiendo en sentido antihorario.

(30)

Los campos descriptos hasta acá contienen datos acerca de la geometría de la región  y   deben   ser   suministrados   por   el   usuario.   Los   campos   que   siguen   contendrán   la  información de los elementos generados por el programa.

Coordenadas de los nodos: En la matriz node_coords se guardarán las coordenadas 

(x, y) de los nodos generados. El tamaño de la matriz será igual a 2nm.

Números   de   los   nodos:  La   matriz  node_nums  contendrá   los   números   enteros  asignados a los nodos para su identificación.

Elementos: Un elemento está representado por un vector que contiene los números de  los nodos que lo definen, por lo tanto el tamaño de la matriz elements será igual a 3 * 

cantidad_de_elementos  ó  4 * cantidad_de_elementos,  según se trate de triángulos o 

cuadriláteros.

Funcionamiento del programa

Para   generar   una   malla   de   elementos   finitos,   el   PROGAM  ejecuta   una   serie   de  funciones en forma secuencial (Figura 16). Estas funciones están implementadas como  subrutinas en el archivo region.f90, y son llamadas desde el archivo progam.f90.  Lectura del archivo de entrada: Consiste en la inicialización de las estructuras de datos  correspondientes a cada región con la información contenida en el archivo de entrada.  Esto lo realiza la subrutina  region_input, que devuelve un vector del tipo region_t, donde  cada elemento corresponde a una región del dominio.

(31)

Figura 16: Diagrama de

flujo del PROGAM secuencial.

Generación   de   los   nodos   en   cada   región:     La   subrutina  region_grid  calcula   las  coordenadas  (x,y)  de los nodos que formarán los elementos, aplicando la interpolación  isoparamétrica presentada en el capítulo anterior. Recibe como argumento una estructura 

region_t y guarda los resultados en el la matriz node_coords.   Es llamada una vez por 

cada región contenida en el vector devuelto por la subrutina region_input.

Numeración de los nodos: La subrutina region_nodenums numera los nodos dentro de  la región que recibe como primer argumento. El segundo parámetro, denominado nstart, 

(32)

indica el primer número disponible (recordemos que esta numeración es global). Si la  región limita con otra identificada por un número de región menor, entonces a los nodos  ubicados   sobre   la   frontera   común   se   les   coloca   los   números   asignados   durante   la  numeración de nodos de la región vecina. Los números de los nodos se guardan en la  matriz node_nums.

Generación   de   los   elementos:   Esta   función   está   implementada   en   la   subrutina 

region_elements. Por cada elemento se define un vector de 3 o 4 enteros, según se trate  de triángulos o cuadriláteros. Este vector contiene los números de los nodos que forman  el elemento. Los vectores de todos los elementos de la región se agrupan en la matriz  elements. Escritura del archivo de salida: La subrutina region_output escribe los resultados en un  archivo de texto, de acuerdo al formato requerido por GiD (el programa utilizado para  visualizar la malla).

(33)

PARALELIZACIÓN DEL PROGAM

Para generar una malla de elementos finitos, el PROGAM paralelo (PROGAM­P) utiliza  un procesador del  cluster  por cada región del dominio. Supongamos que tenemos un  dominio compuesto por cuatro regiones (Figura 17). En este caso, se ejecutarán cuatro  procesos PROGAM­P en sendos nodos del cluster (Figura 18). Cada proceso generará  una malla de elementos finitos dentro de la región que le fue asignada (Figura 19).  Finalmente, uniendo los resultados de los cuatro procesos obtendremos la malla completa  (Figura 20).

Figura 17: Dominio compuesto por

(34)

Figura 18: Para resolver el problema en

paralelo, se necesitan cuatro procesadores.

Figura 19: Cada procesador resuelve una

(35)

Figura 20: Finalmente, se

unifican los resultados.

La   figura  21  ilustra   el   diagrama   de   flujo   del   PROGAM­P.   Los   bloques   en   gris  representan   funciones   que   requieren   comunicación   entre   procesos.   Los   bloques   en  blanco representan subrutinas que utilizan datos de una única región, y su funcionalidad  es independiente de los otros procesos, es por esto que su implementación no difiere de  la versión secuencial. A continuación describiré la implementación de los bloques grises.

Lectura del archivo de entrada

Este bloque está implementado en la subrutina  region_input_p.  El proceso 0 es el  único que tiene habilitado el canal de entrada estándar. Debe leer los datos de entrada y  enviarlos a los procesos correspondientes. La matriz que contiene las coordenadas de los nodos que definen las regiones es   enviada a todos los procesos mediante la rutina MPI_BCAST, el proceso receptor debe  determinar  cuáles corresponden a la región que le toca procesar. Los datos propios de cada región (número de filas, número de columnas, etc) son  transmitidos mediante llamadas a MPI_SEND y MPI_RECV al proceso correspondiente.

(36)

Figura 21: Diagrama de flujo del PROGAM-P resolviendo un problema de tres regiones.

Las líneas rojas indican el pasaje de mensajes entre procesos. Para no complicar el gráfico, no se muestra la comunicación entre los procesos 0 y 2.

Numeración de los nodos

La subrutina region_nodenums_p asigna un número único a cada nodo de la región  que recibe como argumento. El proceso que se sigue es similar al seguido por la rutina  

(37)

figura 8, el proceso encargado de la región 1 numera los nodos compartidos entre ésta y  la región 2. Una vez completada la numeración envía los números al proceso encargado  de la región 2 mediante una llamada a MPI_SEND. Este último debe llamar a MPI_RECV  para obtener los números de los nodos de su frontera superior, y a MPI_SEND para  enviar los números de su frontera izquierda al proceso encargado de la región 3.

Escritura del archivo de salida

La escritura de los resultados está implementada en la subrutina region_output_p. La  información de salida se escribe en un único archivo de texto (output.msh), de acuerdo al  formato requerido por GiD. Para esto deben resolverse dos problemas fundamentales: 1. Acceso al archivo   : Todos los procesos deben escribir en el mismo archivo, por lo  tanto éste deberá ubicarse en un sistema de archivos compartido, como NFS. 2. Sincronización   : para que la salida sea ordenada el proceso n no podrá comenzar  a escribir hasta que el proceso n­1 haya terminado. Este problema se resuelve  mediante la transmisión de un turno, el cual está inicialmente en posesión del  proceso 0.  El siguiente pseudocódigo ilustra el procedimiento propuesto:

(38)

IF (my_rank > 0)  CALL MPI_RECV(my_rank­1, turno) Abrir archivo. Escribir información de salida. Cerrar archivo. IF (my_rank < max_rank) CALL MPI_SEND(my_rank+1, turno)

(39)
(40)

RENDIMIENTO

El cluster del LAPIC

El cluster en el que se realizaron las pruebas está compuesto por 13 computadoras  con procesador Intel Pentium IV 3GHz HT, 1GB de memoria RAM y sistema operativo  GNU/Linux, interconectadas mediante una red Gigabit Ethernet.

La tecnología  Hyper Threading  (HT) de los procesadores Intel permite ejecutar en  forma paralela dos procesos en un mismo procesador. Por lo tanto podemos correr en el  cluster hasta 26 procesos simultáneamente. Para la comunicación entre los procesos se utilizó la biblioteca MPICH2, una de las  implementaciones existentes del estándar MPI­2.0. El PROGAM y el PROGAM­P fueron  compilados con el Intel Fortran Compiler 9.0.

El perfil NACA23012

Con el fin de  ilustrar una  aplicación más compleja, que  suele ser de interés en  problemas de fluidos, escogí el perfil NACA 23012 para mostrar el funcionamiento y las  prestaciones del PROGAM, y comparar el rendimiento de la versión paralela con el de la  versión secuencial. Como es bien conocido en el caso de fluidos, es importante modelar adecuadamente   la capa límite, para lo cual se debe refinar convenientemente la malla alrededor del perfil,  es decir que alrededor del mismo se necesitan elementos muy pequeños. En la figura  22  se muestra una malla simple de 1066 elementos, modelada con el  PROGAM­P   a   partir   de   un   dominio   compuesto   por   20   regiones   (se   ejecutaron   20  procesos).

(41)

Figura 22: Malla compuesta por 1066 elementos.

La figura 23 muestra una malla compuesta por 11438 elementos, generada a partir del  mismo dominio que la anterior.

(42)

Speedup

Se denomina speedup al cociente entre el tiempo que tarda el programa secuencial T   y el tiempo que tarda el programa paralelo   T   en resolver un mismo  problema. El speedup depende del número de procesadores (p) utilizados para resolver el  problema en paralelo y del tamaño del problema (n). En nuestro caso n será igual a la  cantidad de elementos a generar. S n , p= Tn Tn , p  ; 0S n , p≤ p

Eficiencia

La eficiencia del programa paralelo se determina dividiendo el speedup por el número  de procesadores utilizados. E  n , p=S  n , p p = Tn pTn , p  ;  0E n , p≤1 Si la eficiencia alcanza el 100% (E = 1 y S = p) se dice que el programa presenta 

speedup  lineal, ya que  S  será función lineal del tamaño del problema  n. Esta es una 

situación ideal que en la práctica no se consigue, debido a la cantidad de trabajo realizado  por el programa paralelo que no es realizado por el programa secuencial. Esto incluye el   costo de comunicación (determinado por la latencia y el ancho de banda de la red), el  tiempo ocioso (un proceso esperando que otro le envíe algún valor necesario para poder  continuar su ejecución), y los cálculos extra necesarios para resolver el problema en  paralelo.

Rendimiento del PROGAM­P

(43)

Para evaluar el rendimiento del PROGAM­P medí los tiempos que tardan las versiones  paralela   y   secuencial   en   calcular   mallas   de   elementos   finitos   alrededor   del   perfil  NACA23012,   comenzando   con   un   número   pequeño   de   elementos,   e   incrementando  geométricamente   este   valor   hasta   alcanzar   los   14   millones   de   elementos  aproximadamente.   Esta   prueba   está   programada   en   los   fuentes  test.f90  de   ambas 

implementaciones, pudiendo usarse cualquier dominio para realizarla. Al medir el tiempo  de ejecución no se tienen en cuenta las operaciones de entrada y salida. En la figura 24 se muestra un gráfico con los resultados, hecho con Gnuplot. También  se muestran algunos valores aproximados de speedup y eficiencia (Tabla 1). En el gráfico se observa que la solución paralela disminuye notablemente el tiempo de  ejecución, esto también se percibe cuando uno se encuentra frente a la consola del  sistema ejecutando el programa. 

Los valores  de  speedup  obtenidos permiten   afirmar que el programa  paralelo se  ejecuta unas 13 veces más rápido que el secuencial, lo que equivale a una eficiencia del  65% aproximadamente.

(44)

Figura 24: Comparación entre los tiempos que tardan el PROGAM y el PROGAM-P en resolver el perfil NACA23012 a medida que se incrementa la cantidad de elementos.

n S(n,20) E(n,20)

2 millones 13 0,65

6 millones 16,6 0,83

10 millones 13,6 0,68

14 millones 12 0,60

Tabla 1: Valores aproximados de speedup y eficiencia obtenidos mediante la observación del gráfico anterior.

(45)
(46)

CONCLUSIONES

Los clusters beowulf representan una forma sumamente económica de  implementar  un entorno paralelo, ya que están constituidos por hardware disponible en el mercado de  computadoras de escritorio y software libre. Los resultados obtenidos con el PROGAM­P  muestran que se disminuye notablemente el tiempo de ejecución al correr el programa en  un cluster de este tipo.

Sin   embargo,   a   la   hora   de   diseñar   un   algoritmo   para   resolver   un   determinado  problema, es necesario analizar  qué partes del mismo se pueden ejecutar en forma  concurrente y cuáles no, y realizar una estimación del speedup. Esta medida nos dirá qué  tanto más rápido se ejecutará la versión paralela que la secuencial (en algunos casos es  posible que la solución paralela sea más lenta), y así poder decidir se conviene o no  implementar una solución paralela. El PROGAM­P cuenta con una limitación muy seria que deberá ser resuelta en el  futuro, y es que se crea un proceso por cada región del dominio, sin importar la cantidad  de elementos que se deban generar en cada una de ellas. Esto hace que el balance de  carga entre los nodos del  cluster  no sea el óptimo. Además, en el caso de tener más  regiones que procesadores, la biblioteca MPICH les asignará a algunos procesadores  más de un proceso, desbalanceando aún más la carga. 

(47)
(48)

REFERENCIAS

1. Fortran,  http://es.wikipedia.org/wiki/Fortran

2. García Garino, Carlos y Ortiz Andino, C., 1984,  “Descripción del Programa de  

Generación  Automática  de  Mallas (PROGAM). Nota  Técnica  Nº  21”, IMPSA, 

Argentina.  3. GiD – The personal pre and postprocessor, http://gid.cimne.upc.es 4. gnuplot hompage, http://www.gnuplot.info 5. Intel Fortran Compiler for Linux,  http://www.intel.com/support/performancetools/fortran/linux/ 6. León, Oscar A. y García Garino, Carlos , “Programación Paralela con MPI” 7. Message Passing Interface Forum, http://www.mpi­forum.org 8. MPICH2, http://www­unix.mcs.anl.gov/mpi/mpich2/ 9. Rocks Cluster Distribution, http://www.rocksclusters.org 10. Segerlind, L. J., 1976, “Applied Finite Element Analysis”, Wiley, Estados Unidos. 11. The Beowulf Cluster Site, http://www.beowulf.org

(49)
(50)

PROGAM SECUENCIAL

progam.f90

1 ! $Id: progam.f90,v 1.2 2006-07-16 23:07:26 emiliano Exp $ 2 ! PROGAM - Programa de Generación Automática de Mallas 3

4 PROGRAM progam_s 5 USE region_mod 6 IMPLICIT NONE 7

8 TYPE(region_t), ALLOCATABLE, TARGET :: regions(:)

9 INTEGER :: i, n_regions, nstart 10

11 ! Procesamiento de la entrada de datos 12 CALL region_input(regions)

13 n_regions = SIZE(regions) 14

15 ! Divide cada región en elementos cuadrangulares 16 DO i=1, n_regions

17 CALL region_grid(regions(i)) 18 END DO

19

20 ! Asigna un número único a cada nodo 21 nstart = 1

22 DO i=1, n_regions

23 CALL region_nodenums(regions(i), nstart, regions) 24 END DO

25

26 ! Genera los elementos 27 DO i=1, n_regions

28 CALL region_elements(regions(i)) 29 END DO

30

31 ! Salida para GiD

32 CALL region_output(regions) 33

34 END PROGRAM progam_s

region.f90

1 ! $Id: region.f90,v 1.2 2006-07-16 21:51:41 emiliano Exp $ 2 ! PROGAM - Programa de Generación Automática de Mallas 3 4 MODULE region_mod 5 !DEC$ REAL:8 6 IMPLICIT NONE 7 8 TYPE region_t

9 INTEGER :: num ! número de región

10 INTEGER :: n, m ! cantidad de filas y columnas 11 INTEGER :: elem_type ! 3=triángulo, 4=cuadrado 12 INTEGER :: vecinas(4) ! datos de conectividad

13 REAL :: def_nodes(8,2) ! los 8 nodos que definen la región 14 REAL, ALLOCATABLE :: node_coords(:,:,:) ! coordenadas de los nodos

15 INTEGER, ALLOCATABLE :: node_nums(:,:) ! números de los nodos

16 INTEGER, ALLOCATABLE :: elements(:,:) ! cada elemento es un vector de 3 o 4 enteros 17 END TYPE region_t

18

19 CONTAINS

20 ! Procesamiento de la entrada de datos 21 SUBROUTINE region_input(regions)

22 TYPE(region_t), TARGET, ALLOCATABLE, INTENT(OUT) :: regions(:) 23 REAL, ALLOCATABLE :: input_nodes(:,:)

(51)

26 INTEGER :: node_num, i 27

28 READ(*,*) n_regions, n_input_nodes

29 ALLOCATE(regions(n_regions), input_nodes(n_input_nodes,2)) 30

31 DO i=1, n_input_nodes

32 READ(*,*) node_num, input_nodes(i,:) 33 END DO 34 35 DO i=1, n_regions 36 READ(*,*) regions(i)%n,regions(i)%m,regions(i)%elem_type,regions(i)%vecinas,node_nums 37 regions(i)%num = i 38 regions(i)%def_nodes = input_nodes(node_nums,:) 39 END DO

40 END SUBROUTINE region_input 41

42 ! Divide la región en (n-1)*(m-1) elementos cuadrangulares. 43 SUBROUTINE region_grid(this)

44 TYPE(region_t), INTENT(INOUT) :: this 45 INTEGER :: i, j

46 REAL :: pun(8) ! funciones de forma 47 REAL :: eta, si ! coordenadas normalizadas 48 REAL :: deta, dsi ! incrementos de eta y si 49 50 ALLOCATE(this%node_coords(this%n,this%m,2)) 51 ALLOCATE(this%node_nums(this%n,this%m)) 52 53 deta = 2./(this%n-1) 54 dsi = 2./(this%m-1) 55 DO i=1, this%n 56 eta = 1 - (i-1)*deta 57 DO j=1, this%m 58 si = -1 + (j-1)*dsi

59 pun(1) = -0.25 * (1-si) * (1-eta) * (si+eta+1) 60 pun(2) = 0.5 * (1-si**2) * (1-eta)

61 pun(3) = 0.25 * (1+si) * (1-eta) * (si-eta-1) 62 pun(4) = 0.5 * (1+si) * (1-eta**2)

63 pun(5) = 0.25 * (1+si) * (1+eta) * (si+eta-1) 64 pun(6) = 0.5 * (1-si**2) * (1+eta)

65 pun(7) = 0.25 * (1-si) * (1+eta) * (eta-si-1) 66 pun(8) = 0.5 * (1-si) * (1 - eta**2)

67 this%node_coords(i,j,1) = SUM(this%def_nodes(:,1) * pun) 68 this%node_coords(i,j,2) = SUM(this%def_nodes(:,2) * pun) 69 END DO

70 END DO

71 END SUBROUTINE region_grid 72

73 ! Numera en forma global los nodos de la región, teniendo en cuenta los que 74 ! ya han sido numerados por otras regiones. La variable nstart contiene el 75 ! primer número de la secuencia, y devuelve el primer número de la siguiente 76 ! region.

77 SUBROUTINE region_nodenums(this, nstart, regions) 78 TYPE(region_t), TARGET, INTENT(INOUT) :: this 79 INTEGER, INTENT(INOUT) :: nstart 80 TYPE(region_t), INTENT(IN) :: regions(:)

81 INTEGER :: i, j, istart, jstart, iend, jend 82 INTEGER, POINTER :: bound_nums(:)

83 REAL :: chk_node(2), recv_node(2) 84 85 istart=1 86 jstart=1 87 iend=this%n 88 jend=this%m 89

90 ! Verifica si los nodos de las fronteras son numerados por otra región 91 IF (this%vecinas(1)<this%num .AND. this%vecinas(1)/=0) iend=iend-1 92 IF (this%vecinas(2)<this%num .AND. this%vecinas(2)/=0) jend=jend-1 93 IF (this%vecinas(3)<this%num .AND. this%vecinas(3)/=0) istart=istart+1 94 IF (this%vecinas(4)<this%num .AND. this%vecinas(4)/=0) jstart=jstart+1 95

96 ! Numera los nodos 97 DO i=istart, iend

(52)

98 DO j=jstart, jend 99 this%node_nums(i,j) = nstart 100 nstart = nstart+1 101 END DO 102 END DO 103

104 ! Numera las fronteras que no han sido numeradas 105 DO i=1, 4

106 SELECT CASE(i) 107 CASE(1)

108 bound_nums => this%node_nums(this%n,:) ! abajo 109 chk_node = this%node_coords(this%n,1,:)

110 CASE(2)

111 bound_nums => this%node_nums(:,this%m) ! derecha 112 chk_node = this%node_coords(1,this%m,:)

113 CASE(3)

114 bound_nums => this%node_nums(1,:) ! arriba 115 chk_node = this%node_coords(1,1,:)

116 CASE(4)

117 bound_nums => this%node_nums(:,1) ! izquierda 118 chk_node = this%node_coords(1,1,:)

119 END SELECT 120

121 IF (this%vecinas(i)<this%num .AND. this%vecinas(i)/=0) THEN

122 CALL region_bound(regions(this%vecinas(i)), this%num, bound_nums, recv_node) 123 IF (ANY(chk_node/=recv_node)) CALL invertir(bound_nums)

124 END IF 125 END DO

126 END SUBROUTINE region_nodenums 127

128 ! Devuelve los números de nodos que están en la frontera con una región específica. 129 SUBROUTINE region_bound(this, vecina, bound_nums, chk_node)

130 TYPE(region_t), INTENT(IN) :: this ! región origen 131 INTEGER, INTENT(IN) :: vecina ! región destino

132 INTEGER, INTENT(OUT) :: bound_nums(:) ! vector en el que se devuelven los números 133 REAL, INTENT(OUT) :: chk_node(2) ! coordenadas de uno de los extremos 134

135 ! No uso select case porque require expresiones constantes. 136 IF (vecina==this%vecinas(1)) THEN

137 bound_nums = this%node_nums(this%n,:) 138 chk_node = this%node_coords(this%n,1,:) 139 ELSE IF (vecina==this%vecinas(2)) THEN 140 bound_nums = this%node_nums(:,this%m) 141 chk_node = this%node_coords(1,this%m,:) 142 ELSE IF (vecina==this%vecinas(3)) THEN 143 bound_nums = this%node_nums(1,:) 144 chk_node = this%node_coords(1,1,:) 145 ELSE IF (vecina==this%vecinas(4)) THEN 146 bound_nums = this%node_nums(:,1) 147 chk_node = this%node_coords(1,1,:) 148 END IF

149 END SUBROUTINE region_bound 150

151 ! Invierte el orden de los elementos en el vector. 152 SUBROUTINE invertir(vector)

153 INTEGER, INTENT(INOUT) :: vector(:) 154 INTEGER :: aux(SIZE(vector)) 155 INTEGER :: i, n 156 157 n = SIZE(vector) 158 DO i=1, n 159 aux(n-i+1) = vector(i) 160 END DO 161 vector = aux

162 END SUBROUTINE invertir 163

164 ! Genera elementos triangulares (dividiendo los cuadrados según la diagonal más 165 ! corta del elemento (1,1)) o cuadrangulares. Cada elemento es un vector formado 166 ! por los números de los nodos que lo definen.

(53)

170 REAL :: diag1, diag2 171

172 IF (this%elem_type == 3) THEN ! elementos triangulares 173 ALLOCATE(this%elements((this%n-1)*(this%m-1)*2, 3)) 174 k = 1

175 diag1 = SQRT((this%node_coords(1,1,1)-this%node_coords(2,2,1))**2 & 176 + (this%node_coords(1,1,2)-this%node_coords(2,2,2))**2) 177 diag2 = SQRT((this%node_coords(2,1,1)-this%node_coords(1,2,1))**2 & 178 + (this%node_coords(2,1,2)-this%node_coords(1,2,2))**2) 179

180 IF (diag1 < diag2) THEN 181 DO i=1, this%n-1 182 DO j=1, this%m-1 183 this%elements(k,1) = this%node_nums(i,j) 184 this%elements(k,2) = this%node_nums(i+1,j) 185 this%elements(k,3) = this%node_nums(i+1,j+1) 186 k = k+1 187 this%elements(k,1) = this%node_nums(i,j) 188 this%elements(k,2) = this%node_nums(i+1,j+1) 189 this%elements(k,3) = this%node_nums(i,j+1) 190 k = k+1 191 END DO 192 END DO 193 ELSE 194 DO i=1, this%n-1 195 DO j=1, this%m-1 196 this%elements(k,1) = this%node_nums(i,j) 197 this%elements(k,2) = this%node_nums(i+1,j) 198 this%elements(k,3) = this%node_nums(i,j+1) 199 k = k+1 200 this%elements(k,1) = this%node_nums(i+1,j) 201 this%elements(k,2) = this%node_nums(i+1,j+1) 202 this%elements(k,3) = this%node_nums(i,j+1) 203 k = k+1 204 END DO 205 END DO 206 END IF 207

208 ELSE IF (this%elem_type == 4) THEN ! elementos cuadrangulares 209 ALLOCATE(this%elements((this%n-1)*(this%m-1), 4)) 210 k = 1 211 DO i=1, this%n-1 212 DO j=1, this%m-1 213 this%elements(k,1) = this%node_nums(i,j) 214 this%elements(k,2) = this%node_nums(i+1,j) 215 this%elements(k,3) = this%node_nums(i+1,j+1) 216 this%elements(k,4) = this%node_nums(i,j+1) 217 k = k+1 218 END DO 219 END DO 220 END IF

221 END SUBROUTINE region_elements 222

223 ! Salida para GiD

224 SUBROUTINE region_output(regions)

225 TYPE(region_t), INTENT(IN) :: regions(:)

226 INTEGER :: i, j, k, elem_num, node_num 227 228 elem_num = 1 229 node_num = 0 230 OPEN(UNIT=1, FILE='output.msh') 231 232 DO k=1, SIZE(regions) 233 ! Cabecera 234 IF (regions(k)%elem_type==3) THEN

235 WRITE(1,*) 'MESH dimension 2 ElemType Triangle Nnode 3' 236 ELSE IF (regions(k)%elem_type==4) THEN

237 WRITE(1,*) 'MESH dimension 2 ElemType Quadrilateral Nnode 4' 238 END IF

239

240 ! Coordenadas de los nodos 241 WRITE(1,*) 'coordinates'

(54)

242 WRITE(1,*) '# num x y' 243 DO i=1, regions(k)%n 244 DO j=1, regions(k)%m

245 IF (regions(k)%node_nums(i,j)>node_num) THEN 246 node_num = regions(k)%node_nums(i,j)

247 WRITE(1,*) node_num, regions(k)%node_coords(i,j,:) 248 END IF

249 END DO 250 END DO

251 WRITE(1,*) 'end coordinates' 252

253 ! Elementos

254 WRITE(1,*) 'elements'

255 WRITE(1,*) '# num nodo1 nodo2 nodo3 nodo4' 256 DO i=1, SIZE(regions(k)%elements, DIM=1) 257 WRITE(1,*) elem_num, regions(k)%elements(i,:) 258 elem_num = elem_num+1

259 END DO

260 WRITE(1,*) 'end elements' 261 WRITE(1,*)

262 END DO

263 END SUBROUTINE region_output 264 END MODULE region_mod

test.f90

1 ! $Id: test.f90,v 1.1.1.1 2006-07-16 21:18:46 emiliano Exp $ 2 ! PROGAM - Programa de Generación Automática de Mallas 3

4 ! Esta prueba genera sucesivamente mallas de elementos finitos, 5 ! aumentando la cantidad de elementos geométricamente. Para cada 6 ! malla se toma el tiempo de cálculo, sin considerar la entrada 7 ! ni la salida de datos. 8 9 PROGRAM progam_s_test 10 USE region_mod 11 IMPLICIT NONE 12

13 TYPE(region_t), ALLOCATABLE, TARGET :: regions(:)

14 INTEGER :: i, n_regions, nstart 15 REAL :: ti, tf

16 INTEGER :: ierr, k

17 INTEGER, ALLOCATABLE :: n_init(:), m_init(:) 18

19 ! Procesamiento de la entrada de datos 20 CALL region_input(regions) 21 n_regions = SIZE(regions) 22 23 ALLOCATE(n_init(n_regions), m_init(n_regions)) 24 n_init = regions%n 25 m_init = regions%m 26 27 DO k=1, 100 28 regions%n = n_init*k 29 regions%m = m_init*k 30 31 CALL CPU_TIME(ti)

32 ! Divide cada región en elementos cuadrangulares 33 DO i=1, n_regions

34 CALL region_grid(regions(i)) 35 END DO

36

37 ! Asigna un número único a cada nodo 38 nstart = 1

39 DO i=1, n_regions

40 CALL region_nodenums(regions(i), nstart, regions) 41 END DO

(55)

45 CALL region_elements(regions(i)) 46 END DO

47 CALL CPU_TIME(tf) 48

49 WRITE(*,*) DOT_PRODUCT(regions%n, regions%m), tf-ti 50 DO i=1, n_regions 51 DEALLOCATE(regions(i)%node_coords) 52 DEALLOCATE(regions(i)%node_nums) 53 DEALLOCATE(regions(i)%elements) 54 END DO 55 END DO 56

(56)

PROGAM PARALELO

progam.f90

1 ! $Id: progam.f90,v 1.2 2006-07-17 00:52:06 emiliano Exp $

2 ! PROGAM-P - Programa de Generacin Automática de Mallas en Paralelo 3 4 PROGRAM progam_p 5 !DEC$ REAL:8 6 USE region_mod 7 IMPLICIT NONE 8 9 TYPE(region_t) :: region_local 10 INTEGER :: ierr 11 12 CALL MPI_INIT(ierr) 13

14 CALL region_init_mod() ! Inicialización 15 CALL region_input_p(region_local) ! Entrada de datos

16 CALL region_grid(region_local) ! Creación de los nodos interiores 17 CALL region_nodenums_p(region_local) ! Numeración de los nodos

18 CALL region_elements(region_local) ! Creación de los elementos 19 CALL region_output_p(region_local) ! Salida para GiD

20

21 CALL MPI_FINALIZE(ierr) 22

23 END PROGRAM progam_p

region.f90

1 ! $Id: region.f90,v 1.2 2006-07-17 00:52:06 emiliano Exp $ 2 ! PROGAM-P(Programa de Generacin Automática de Mallas en Paralelo) 3 4 MODULE region_mod 5 !DEC$ REAL:8 6 IMPLICIT NONE 7 INCLUDE 'mpif.h' 8 9 TYPE region_t

10 INTEGER :: num ! número de región

11 INTEGER :: n, m ! cantidad de filas y columnas 12 INTEGER :: elem_type ! 3=triángulo, 4=cuadrado 13 INTEGER :: vecinas(4) ! datos de conectividad

14 REAL :: def_nodes(8,2) ! los 8 nodos que definen la región 15 REAL, ALLOCATABLE :: node_coords(:,:,:) ! coordenadas de los nodos

16 INTEGER, ALLOCATABLE :: node_nums(:,:) ! números de los nodos

17 INTEGER, ALLOCATABLE :: elements(:,:) ! cada elemento es un vector de 3 o 4 enteros 18 END TYPE region_t

19

20 INTEGER, PRIVATE :: my_rank, p, status(MPI_STATUS_SIZE), ierr 21

22 CONTAINS

23 ! Inicialización

24 SUBROUTINE region_init_mod()

25 CALL MPI_COMM_RANK(MPI_COMM_WORLD, my_rank, ierr) 26 CALL MPI_COMM_SIZE(MPI_COMM_WORLD, p, ierr) 27 END SUBROUTINE region_init_mod

28

29 ! Procesamiento de la entrada de datos 30 SUBROUTINE region_input_p(this) 31 TYPE(region_t), INTENT(OUT) :: this 32 INTEGER :: i

(57)

36 INTEGER :: vecinas(4) 37 INTEGER :: node_nums(8) 38

39 IF (my_rank == 0) THEN

40 READ(*,*) n_regions, n_input_nodes 41 IF (n_regions /= p) THEN

42 WRITE(*,*) 'El número de procesos es distinto del número de regiones.' 43 CALL MPI_ABORT(MPI_COMM_WORLD, 1, ierr)

44 ENDIF 45 ENDIF 46

47 CALL MPI_BCAST(n_regions, 1, MPI_INTEGER, 0, MPI_COMM_WORLD, ierr) 48 CALL MPI_BCAST(n_input_nodes, 1, MPI_INTEGER, 0, MPI_COMM_WORLD, ierr) 49 ALLOCATE(input_nodes(n_input_nodes,2))

50

51 ! Lectura de los nodos y sus coordenadas 52 IF (my_rank == 0) THEN

53 DO i=1, n_input_nodes

54 READ(*,*) node_num, input_nodes(i,:) 55 END DO

56 END IF 57

58 ! Los nodos se envían a todos los procesos, sin importar cuáles necesiten. 59 CALL MPI_BCAST(input_nodes, SIZE(input_nodes), MPI_REAL8, 0, MPI_COMM_WORLD, ierr) 60

61 IF (my_rank == 0) THEN

62 ! El proceso 0 se queda con la primera región.

63 READ(*,*) this%n, this%m, this%elem_type, this%vecinas, node_nums 64 this%def_nodes = input_nodes(node_nums,:)

65

66 ! Lee los datos de las regiones restantes y los envía al proceso 67 ! correspondiente.

68 DO i=2, n_regions

69 READ(*,*) n, m, elem_type, vecinas, node_nums

70 CALL MPI_SEND(n, 1, MPI_INTEGER, i-1, 0, MPI_COMM_WORLD, ierr) 71 CALL MPI_SEND(m, 1, MPI_INTEGER, i-1, 0, MPI_COMM_WORLD, ierr) 72 CALL MPI_SEND(elem_type, 1, MPI_INTEGER, i-1, 0, MPI_COMM_WORLD, ierr) 73 CALL MPI_SEND(vecinas, 4, MPI_INTEGER, i-1, 0, MPI_COMM_WORLD, ierr) 74 CALL MPI_SEND(node_nums, 8, MPI_INTEGER, i-1, 0, MPI_COMM_WORLD, ierr) 75 END DO

76

77 ELSE

78 ! Recibe los datos enviados por el proceso 0.

79 CALL MPI_RECV(this%n, 1, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, status, ierr) 80 CALL MPI_RECV(this%m, 1, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, status, ierr)

81 CALL MPI_RECV(this%elem_type, 1, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, status, ierr) 82 CALL MPI_RECV(this%vecinas, 4, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, status, ierr) 83 CALL MPI_RECV(node_nums, 8, MPI_INTEGER, 0, 0, MPI_COMM_WORLD, status, ierr) 84 this%def_nodes = input_nodes(node_nums,:)

85 END IF 86

87 this%num = my_rank + 1 88

89 END SUBROUTINE region_input_p 90

91 ! Divide la región en (n-1)*(m-1) elementos cuadrangulares. 92 SUBROUTINE region_grid(this)

93 TYPE(region_t), INTENT(INOUT) :: this 94 INTEGER :: i, j

95 REAL :: pun(8) ! funciones de forma 96 REAL :: eta, si ! coordenadas normalizadas 97 REAL :: deta, dsi ! incrementos de eta y si 98 99 ALLOCATE(this%node_coords(this%n,this%m,2)) 100 ALLOCATE(this%node_nums(this%n,this%m)) 101 102 deta = 2./(this%n-1) 103 dsi = 2./(this%m-1) 104 DO i=1, this%n 105 eta = 1 - (i-1)*deta 106 DO j=1, this%m 107 si = -1 + (j-1)*dsi

(58)

108 pun(1) = -0.25 * (1-si) * (1-eta) * (si+eta+1) 109 pun(2) = 0.5 * (1-si**2) * (1-eta)

110 pun(3) = 0.25 * (1+si) * (1-eta) * (si-eta-1) 111 pun(4) = 0.5 * (1+si) * (1-eta**2)

112 pun(5) = 0.25 * (1+si) * (1+eta) * (si+eta-1) 113 pun(6) = 0.5 * (1-si**2) * (1+eta)

114 pun(7) = 0.25 * (1-si) * (1+eta) * (eta-si-1) 115 pun(8) = 0.5 * (1-si) * (1 - eta**2)

116 this%node_coords(i,j,1) = SUM(this%def_nodes(:,1) * pun) 117 this%node_coords(i,j,2) = SUM(this%def_nodes(:,2) * pun) 118 END DO

119 END DO

120 END SUBROUTINE region_grid 121

122 ! Numera en forma global los nodos de la región, teniendo en cuenta los que 123 ! ya han sido numerados por otras regiones.

124 SUBROUTINE region_nodenums_p(this)

125 TYPE(region_t), INTENT(INOUT), TARGET :: this 126 INTEGER :: i, j, istart, jstart, iend, jend

127 INTEGER :: nstart ! La numeración comienza a partir de este número. 128 INTEGER :: nend ! Este valor se envía a la siguiente región. 129 INTEGER :: ranks(4) ! Ranks de las regiones vecinas.

130 INTEGER, POINTER :: bound_nums(:)

131 REAL :: chk_node(2), recv_node(2) 132 133 nstart = 1 134 istart = 1 135 jstart = 1 136 iend = this%n 137 jend = this%m 138

139 ! Verifica si los nodos de las fronteras son numerados por otra región. 140 IF (this%vecinas(1)<this%num .AND. this%vecinas(1)/=0) iend=iend-1 141 IF (this%vecinas(2)<this%num .AND. this%vecinas(2)/=0) jend=jend-1 142 IF (this%vecinas(3)<this%num .AND. this%vecinas(3)/=0) istart=istart+1 143 IF (this%vecinas(4)<this%num .AND. this%vecinas(4)/=0) jstart=jstart+1 144

145 ! Obtiene el valor nstart

146 IF (my_rank>0) CALL MPI_RECV(nstart, 1, MPI_INTEGER, my_rank-1, 0, MPI_COMM_WORLD, status, ierr)

147 nend = nstart + (iend-istart+1) * (jend-jstart+1)

148 IF (my_rank<p-1) CALL MPI_SEND(nend, 1, MPI_INTEGER, my_rank+1, 0, MPI_COMM_WORLD, ierr) 149

150 ! Numera los nodos 151 DO i=istart, iend 152 DO j=jstart, jend 153 this%node_nums(i,j) = nstart 154 nstart = nstart+1 155 END DO 156 END DO 157

158 ! Recibe los nodos de las fronteras numeradas por una región vecina. 159 ! El chk_node es para verificar que el vector recibido esté en el orden 160 ! correcto. Los mpi_recv se hacen antes que los mpi_send para contemplar 161 ! el caso de que un nodo ubicado en una esquina, sea numerado por una 162 ! región vecina y deba ser enviado a otra región vecina.

163 ranks = this%vecinas - 1 164 DO i=1, 4

165 SELECT CASE(i) 166 CASE(1)

167 bound_nums => this%node_nums(this%n,:) ! abajo 168 chk_node = this%node_coords(this%n,1,:)

169 CASE(2)

170 bound_nums => this%node_nums(:,this%m) ! derecha 171 chk_node = this%node_coords(1,this%m,:)

172 CASE(3)

173 bound_nums => this%node_nums(1,:) ! arriba 174 chk_node = this%node_coords(1,1,:)

175 CASE(4)

(59)

179

180 IF (this%vecinas(i)<this%num .AND. this%vecinas(i)/=0) THEN

181 CALL MPI_RECV(bound_nums, SIZE(bound_nums), MPI_INTEGER, ranks(i), 0, MPI_COMM_WORLD, status, ierr)

182 CALL MPI_RECV(recv_node, 2, MPI_REAL8, ranks(i), 1, MPI_COMM_WORLD, status, ierr) 183 IF (ANY(chk_node/=recv_node)) CALL invertir(bound_nums)

184 END IF 185 END DO 186

187 ! Envía los números de las fronteras a las regiones vecinas que tengan mayor 188 ! número de región.

189 DO i=1, 4 190 SELECT CASE(i) 191 CASE(1)

192 bound_nums => this%node_nums(this%n,:) ! abajo 193 chk_node = this%node_coords(this%n,1,:)

194 CASE(2)

195 bound_nums => this%node_nums(:,this%m) ! derecha 196 chk_node = this%node_coords(1,this%m,:)

197 CASE(3)

198 bound_nums => this%node_nums(1,:) ! arriba 199 chk_node = this%node_coords(1,1,:)

200 CASE(4)

201 bound_nums => this%node_nums(:,1) ! izquierda 202 chk_node = this%node_coords(1,1,:)

203 END SELECT 204

205 IF (this%vecinas(i)>this%num) THEN

206 CALL MPI_SEND(bound_nums, SIZE(bound_nums), MPI_INTEGER, ranks(i), 0, MPI_COMM_WORLD, ierr)

207 CALL MPI_SEND(chk_node, 2, MPI_REAL8, ranks(i), 1, MPI_COMM_WORLD, ierr) 208 END IF

209 END DO

210 END SUBROUTINE region_nodenums_p 211

212 ! Invierte el orden de los elementos en el vector. 213 SUBROUTINE invertir(vector)

214 INTEGER, INTENT(INOUT) :: vector(:) 215 INTEGER :: aux(SIZE(vector)) 216 INTEGER :: i, n 217 218 n = SIZE(vector) 219 DO i=1, n 220 aux(n-i+1) = vector(i) 221 END DO 222 vector = aux

223 END SUBROUTINE invertir 224

225 ! Genera elementos triangulares (dividiendo los cuadrados según la diagonal más 226 ! corta del elemento (1,1)) o cuadrangulares. Cada elemento es un vector formado 227 ! por los números de los nodos que lo definen.

228 SUBROUTINE region_elements(this)

229 TYPE(region_t), INTENT(INOUT) :: this 230 INTEGER :: i, j, k 231 REAL :: diag1, diag2 232

233 IF (this%elem_type == 3) THEN ! elementos triangulares 234 ALLOCATE(this%elements((this%n-1)*(this%m-1)*2, 3)) 235 k = 1

236 diag1 = SQRT((this%node_coords(1,1,1)-this%node_coords(2,2,1))**2 & 237 + (this%node_coords(1,1,2)-this%node_coords(2,2,2))**2) 238 diag2 = SQRT((this%node_coords(2,1,1)-this%node_coords(1,2,1))**2 & 239 + (this%node_coords(2,1,2)-this%node_coords(1,2,2))**2) 240

241 IF (diag1 < diag2) THEN 242 DO i=1, this%n-1 243 DO j=1, this%m-1 244 this%elements(k,1) = this%node_nums(i,j) 245 this%elements(k,2) = this%node_nums(i+1,j) 246 this%elements(k,3) = this%node_nums(i+1,j+1) 247 k = k+1 248 this%elements(k,1) = this%node_nums(i,j)

Referencias

Documento similar

Abstract: This paper reviews the dialogue and controversies between the paratexts of a corpus of collections of short novels –and romances– publi- shed from 1624 to 1637:

Después de una descripción muy rápida de la optimización así como los problemas en los sistemas de fabricación, se presenta la integración de dos herramientas existentes

Habiendo organizado un movimiento revolucionario en Valencia a principios de 1929 y persistido en las reuniones conspirativo-constitucionalistas desde entonces —cierto que a aquellas

Luis Miguel Utrera Navarrete ha presentado la relación de Bienes y Actividades siguientes para la legislatura de 2015-2019, según constan inscritos en el

Fuente de emisión secundaria que afecta a la estación: Combustión en sector residencial y comercial Distancia a la primera vía de tráfico: 3 metros (15 m de ancho)..

Así, hasta la introducción del Bre- viario de san Pío V (1568), a raíz de la reforma tridentina, siguió cantán- dose y representádose, no sólo en Mallorca (Catedral y otras

La campaña ha consistido en la revisión del etiquetado e instrucciones de uso de todos los ter- mómetros digitales comunicados, así como de la documentación técnica adicional de

En realidad, al encontrarse el conductor parcialmente rodeado de un material magnético como el hierro, el campo magnético tenderá a anularse en las zonas exteriores del hierro, por