3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO...

37
3 3 3 . . . CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO PETSC. 64

Transcript of 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO...

Page 1: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

333... CCÁÁLLCCUULLOO NNUUMMÉÉRRIICCOO EENN PPAARRAALLEELLOOAASSIISSTTIIDDOO PPOORR LLAA LLIIBBRREERRÍÍAA DDEE CCÁÁLLCCUULLOO

PPEETTSSCC..

64

Page 2: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.1. INTRODUCCIÓN.

Hemos llegado a la parte central del proyecto, que consiste en hacer uso del cálculo numérico asistido por la librería PETSc para el Cálculo de Estructuras. Para llegar a entender esto, es necesario hacer una explicación más o menos detallada del funcionamiento de PETSc y de las posibilidades que ofrece.

PETSc proporciona diversos paquetes software para la solución escalable (en paralelo) de sistemas algebraicos, provenientes de discretizaciones de ecuaciones diferenciales parciales (PDE’s), así como bloques de construcción para la optimización y cálculo de autovalores.

Se apoya en MPI para las operaciones de comunicación, pero esconde la interfaz de programación de MPI mediante un envoltorio que añade la propia PETSc.

La librería PETSc está muy documentada, es de libre distribución y totalmente portable para las plataformas Cray, SGI, IBM, HP, Sun, Linux, Windows y Apple. Los lenguajes para escritura de código posibles son Fortran, C y C++. El origen de PETSc es el fruto de diversas investigaciones del Departamento de Energía de los Estados Unidos.

Algunas de las herramientas que contiene la librería paralela son: diversas operaciones paralelas de manejo de vectores / arrays, numerosas operaciones y formatos de matrices paralelas y no paralelas, solucionadotes (solver) lineales y no lineales, algunos integradores de ecuaciones diferenciales ordinarias, gestión limitada de datos / rejillas paralelas, etc.

La estructura de la librería PETSc se presenta en la figura 24.

Figura 24. Jerarquía de los diversos componentes que componen PETSc.

65

Page 3: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.2. MODELO DE PROGRAMACIÓN DE PETSC.

PETSc utiliza la filosofía de la programación orientada a objetos (OOP) para construir las aplicaciones paralelas. El diseño de aplicaciones no se centra en los datos de los objetos, sino más bien en las operaciones que se pueden realizar sobre dichos datos. Por ejemplo, un vector no es un array de números de una dimensión, sino un objeto abstracto sobre el que se definen operaciones tales como el producto escalar o la suma.

El modelo de programación con el que se trabaja presenta las siguientes características:

Sistema de memoria distribuida, no compartida: solamente se requiere un compilador estándar, todas las operaciones de acceso a datos en máquinas remotas se realizan mediante MPI. Todos los detalles de las comunicaciones se esconden dentro de los objetos, de modo que el usuario no utiliza llamadas directas a MPI, sino que realiza las operaciones de comunicaciones desde un nivel más alto. No obstante, es posible realizar llamadas a rutinas de MPI desde cualquier parte del código. Se utiliza un modelo de programación SPMD (Simple Program Multiple Data), es decir, todos los nodos que pertenecen al cluster, dentro del cual ejecutamos nuestras aplicaciones paralelas, corren el mismo programa; sin embargo, cada proceso que se ejecuta en cada nodo tiene sus propias variables, distintas a las del resto de nodos, aun cuando se llamen igual. Esto hace que el resultado de ejecutar el mismo programa en diversos nodos sea distinto en cada uno. Lo que cabe ahora cuestionarse es cómo se bifurca el programa si todos los procesos ejecutan el mismo, de modo que cada proceso ejecute la parte del código paralelo que le corresponde. La forma de hacer esto es mediante la variable rank, que identifica el orden de cada proceso en el entorno de comunicaciones MPI. En función de esta variable se pueden poner en el código sentencias de control de flujo para ramificar el programa. Dentro de PETSc hay rutinas que son colectivas y otras que no lo son. Las operaciones colectivas se llevan a cabo mediante MPI y, en concreto, los comunicadores, que representan a todos aquellos procesos que tendrán que estarán involucrados en la computación. Los constructores de objetos PETSc son colectivos, y necesitan que se les pase un argumento que sea el nombre de un comunicador MPI para poder realizar su función. El comunicador que se utilizaba en MPI que involucraba a todos los procesos era MPI_COMM_WORLD, que ahora es sustituido por ‘PETSC_COMM_WORLD’. Si dentro del código existe una secuencia de órdenes colectivas, todos los procesos involucrados en las operaciones han de llamar a dichas operaciones en el mismo orden.

En ningún momento hay que perder de vista que PETSc es solamente una librería de funciones (en C) y subrutinas (en Fortran), que proporcionan a nuestro código de aplicación la capacidad de ejecución de cálculo en paralelo mediante el soporte de otras librerías, como son MPI, BLAS y LAPACK.

También destaca una ventaja importante de PETSc, y es que las interfaces de las funciones y subrutinas disponibles son las mismas para C, C++ y Fortran, con algún pequeño matiz que en su momento se comentará.

66

Page 4: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.3. PANORÁMICA DE PETSC.

PETSc está compuesta de un conjunto de librerías, de la misma forma que las clases en C++ o JAVA. Algunos de los módulos que se incluyen en PETSc son:

Vectores.Matrices. Conjunto de índices. Arrays distribuidos. Métodos del subespacio de Krylov (solvers lineales). Precondicionadores.Solvers no lineales. Timesteppers para resolver PDE’s dependientes del tiempo.

La jerarquía a la que obedecen todas estas librerías se presenta en la figura 25.

Figura 25. Librerías que componen PETSc.

En la ruta donde se encuentra instalada PETSc, hay una estructura de subdirectorios que da una idea de la organización de PETSc. Los subdirectorios que dependen del directorio raíz son:

67

Page 5: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

docs: aquí se encuentra toda la documentación de PETSc. El subdirectorio ‘manualpages’ contiene un completo manual ‘HTML’ de las rutinas PETSc.

bin: utilidades para el uso con PETSc.

bmake: directorio base de los makefiles necesarios para compilar código PETSc. Incluye subdirectorios para diferentes configuraciones.

include: todos los ficheros de cabecera para incluir que son visibles por el usuario.

include/finclude: ficheros de cabecera para usuario Fortran.

include/private: ficheros privados que no deberían utilizarse para programas de aplicación.

src: aquí se encuentra todo el código fuente para todas las librerías de PETSc, que incluye:

- vec: vectores. is: conjuntos de índices.

- mat: matrices. - dm:

da: arrays distribuidos. ao: ordenaciones de aplicación.

- ksp:ksp: aceleradores del Subespacio de Krylov. pc: precondicionadores.

- snes: solvers no lineales. - ts: solvers de EDO’s y timestepping. - sys: rutinas generales relacionadas con el sistema:

plog: rutinas de profiling y registro de PETSc. draw: gráficos simples.

- fortran: soporte para interfaces Fortran. - contrib: módulos anejos a PETSc, que no son parte oficial de la librería.

examples: programas de ejemplo.

interface: secuencias de llamada de interfaz abstracta. Este código no atiende a implementaciones particulares.

impls: código fuente de algunas implementaciones.

utils: rutinas de utilidad.

Los nombres de directorio marcados en negrita, son aquellos que han sido de gran utilidad a la hora de comprender y utilizar la librería paralela PETSc en el proyecto que tenemos entre manos.

68

Page 6: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.3.1. INICIO Y TERMINACIÓN DE UN PROGRAMA PETSC.

Al igual que ocurría en el caso de MPI, a la hora de escribir programas utilizando PETSc, hay que llamar a una rutina de inicialización del entorno y a otra de terminación. Estas son, respectivamente:

PetscInitialize(), que establece los servicios y datos estáticos e inicializa MPI si no se ha hecho antes (‘PetscInitialize’ llama automáticamente a ‘MPI_Init’). PetscFinalize(), que presenta el resumen de registro de eventos a lo largo del programa y cierra y libera los recursos utilizados.

NOTA: Al igual que con MPI, hay un manual en formato ‘HTML’ (al que se ha hecho referencia en el apartado anterior) donde se encuentra la definición detallada de todas y cada una de las funciones de PETSc. Es un manual muy completo y útil. También se ha hecho una copia, que se encuentra en el directorio “unidadCD:\Documentación\manual_web_funciones_PETSc”. A la hora de programar, se aconseja acudir antes al manual citado antes de utilizar cualquier función.

Ya hemos dicho que todas las rutinas de comunicación colectiva de PETSc hacen uso de MPI. No es necesario utilizar rutinas de paso de mensajes de MPI directamente aunque, a veces, no queda otra salida, por ejemplo en el caso de ‘MPI_BARRIER’, ya que PETSc no tiene envoltorio para dicha rutina. Desde cualquier parte del código se puede invocar a cualquier rutina MPI, ya que el fichero de definiciones ‘mpi.h’ se incluye dentro de los ficheros de cabecera de PETSc. No obstante, en general, se hace poco uso directo de rutinas MPI, más bien se utilizan las rutinas de envoltorio de PETSc, lo que nos sitúa en un nivel más alto de abstracción.

Se puede llamar a la rutina ‘MPI_Initialize’ antes de llamar a ‘PetscInitialize’ (por cuenta del usuario o porque lo hace otro paquete externo a PETSc), pero si se hace así, al llamar a ‘PetscFinalize’ al final del programa, habrá que llamar también a ‘MPI_Initialize’.

Las librerías de PETSc se encargan de mapear todas las variables análogas a las de MPI; tal es el caso del comunicador ‘PETSC_COMM_WORLD’, ya citado, que se mapea al comunicador ‘MPI_COMM_WORLD’. Este comunicador hace referencia a todos los procesos que intervienen en la computación o en las comunicaciones. Si quisiéramos referirnos a un solo proceso, utilizaríamos el comunicador ‘PETSC_COMM_SELF’, que hace referencia al proceso llamante de la rutina PETSc actualmente llamada.

3.3.2. CHEQUEO DE ERRORES.

Todas las rutinas PETSc, al igual que ocurría con MPI, devuelven un código de error (variable del tipo ‘PestscErrorCode’). En el caso de C, el valor se devuelve como resultado de la función llamada; para Fortran, el código se devuelve dentro de una variable pasada como argumento a la subrutina. Ya se ha comentado la ventaja de que la interfaz de las funciones C y las subrutinas Fortran es la misma, con la pequeña diferencia de que las subrutinas Fortran necesitan un argumento más que es este código de error. El código de error es ‘0’ si todo ha ido bien, y de valor distinto de ‘0’ si ha habido algún problema.

NOTA: Al igual que con MPI, hay un manual en formato ‘HTML’ (al que se ha hechoreferencia en el apartado anterior) donde se encuentra la definición detallada de todas y cada una de las funciones de PETSc. Es un manual muy completo y útil.También se ha hecho una copia, que se encuentra en el directorio“unidadCD:\Documentación\manual_web_funciones_PETSc”. A la hora deprogramar, se aconseja acudir antes al manual citado antes de utilizar cualquier función.

69

Page 7: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

Es un error muy común en códigos PETSc escritos en Fortran olvidarse del último argumento de las subrutinas, el código de error.

La macro ‘CHKERR(ierr)’ incluida con PETSc, verifica el valor del código de error devuelto por las rutinas. En el caso de códigos escritos en C, si hay algún error, esta rutina termina todos los procesos e imprime en pantalla una traza de errores detectados. En el caso de Fortran no ocurre igual; sí que se terminan todos los procesos, pero no se imprime una traza de errores porque no está soportado por las rutinas Fortran. No obstante, es fácil escribir un trozo de código que en función del valor del código de error imprima o no una traza de errores.

Existe otra macro, ‘SETERRQ’, la cual puede lanzar códigos de error cuando el usuario la utilice. Como antes, en el caso de Fortran, a diferencia de C, esta macro tampoco imprime una traza de los errores encontrados.

3.3.3. OTROS ASPECTOS IMPORTANTES DE LOS PROGRAMAS PETSC.

3.3.3.1. FICHEROS DE CABECERA PETSC.

Los ficheros de cabecera, correspondientes a los módulos PETSc que se desean utilizar en nuestro programa de aplicación, han de incluirse al principio del código.

Para el caso de programación en lenguaje C o C++, las sentencias de inclusión son de la forma:

#include “petscksp.h”

En la sentencia de arriba, ‘petscksp.h’ es el fichero de cabecera para el módulo PETSc correspondiente a la librería de solvers lineales, que utilizan métodos del Subespacio de Krylov.

Como ya se ha visto, las distintas librerías que componen PETSc forman una jerarquía bien definida, que hace que las librerías de más alto nivel incluyan a las de niveles inferiores. Siendo esto así, cada vez que vayamos a crear un programa de aplicación, se habrá de incluir el fichero de cabecera del módulo PETSc de más alto nivel que vayamos a utilizar. El resto de módulos de más bajo nivel que se necesiten para la compilación serán automáticamente incluidos. Así, ‘petscksp.h’ incluye ‘petscmat.h’ (fichero de cabecera para matrices), este a ‘petscvec.h’ (vectores) y este último a ‘petsc.h’ (fichero base de la librería PETSc). Todos estos ficheros y los que no se han nombrado se encuentran en el subdirectorio “include” del directorio de instalación raíz de PETSc.

El caso de Fortran es algo distinto. Los ficheros de cabecera se encuentran en el subdirectorio “include/finclude”, y la forma de incluirlos es la misma que en el caso de C/C++. El problema que se presenta al programar con Fortran es que los ficheros de cabecera no pueden ser incluidos más de una vez. Es por esta razón por la que se han de incluir los ficheros de cabecera de todos aquellos módulos PETSc que se necesiten, teniendo ahora en cuenta que los módulos de alto nivel no incluyen a los de bajo nivel (aunque sí que hacen uso de ellos).

Otro punto importante a tener en cuenta al programar con Fortran, es que los ficheros de código fuente han de tener la extensión ‘.F’ en vez de ‘.f’. Esto habilita el uso del preprocesador de C, de modo que se permite el uso de sentencias “#include”

70

Page 8: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

dentro del código Fortran. Esto es necesario porque PETSc está escrita en C, y los ficheros de cabecera han de procesarse antes de enlazar las librerías con nuestro código Fortran de aplicación. Si a todo esto le añadimos que en vez de programar en Fortran 77 programamos en Fortran 90, por ejemplo, entonces la extensión de los ficheros será ‘.F90’ en lugar de ‘.f’, de modo que le indiquemos al compilador de Fortran (‘g95’) que el código escrito es Fortran 90 y no 77.

NOTA: En el capítulo 11, del manual de usuario de PETSc incluido en la ruta “unidadCD:\Documentación\PETSc”, tienen lugar diversas discusiones sobre la utilización de Fortran con PETSc.

3.3.3.2. OPCIONES EN TIEMPO DE EJECUCIÓN.

En el ANEXO I, donde se explica la implantación del sistema, se muestra la forma de ejecutar un programa paralelo por medio de utilidades que acompañan al paquete software de MPI. Si a la hora de lanzar un programa paralelo le pasamos como argumento en línea de comandos la opción “-help”, el programa imprimirá una lista con todas las opciones disponibles que se le pueden pasar como argumentos del programa. Estas opciones se denominan “opciones en tiempo de ejecución”, ya que se le pasan al programa una vez se está ejecutando. Estas mismas opciones se pueden especificar dentro del código fuente, de modo que se incluyen en el programa en tiempo de compilación.

Cada programa PETSc, mantiene una base de datos con los nombres y valores de las opciones que se pueden escoger. Esta base de datos se crea al llamar a la rutina de inicialización ‘PetscInitialize()’. Es necesario ver la definición de la rutina en C/C++ y Fortran:

C/C++: PetscInitialize(int *argc, char **args, const char *file, const char *help) Fortran: PetscInitialize(character file, integer ierr)

En el caso de C/C++, ‘argc’ y ‘argv’ son las direcciones de los argumentos de línea de comandos pasados al programa, y ‘file’ es el nombre de un fichero que contiene opciones adicionales. Además, se pueden especificar opciones al programar a través de la variable de entorno ‘PETSC_OPTIONS’. El orden de procesado de las opciones es:

1. Fichero.2. Variable de entorno. 3. Línea de comandos.

La formato habitual a la hora de especificar opciones es: -módulo_opción valor, por ejemplo “-ksp_type richardson”.

Cualquier subrutina en un programa PETSc puede añadir registros a la base de datos por medio del comando ‘PetscOptionsSetValue(char *name, char *value)’, aunque a penas se utiliza. Por el contrario, obtener opciones de la base de datos es muy común, y se realiza por medio de alguna de las siguientes rutinas, dependiendo del valor que queramos obtener:

‘PetscOptionsHasName’

NOTA: En el capítulo 11, del manual de usuario de PETSc incluido en la ruta“unidadCD:\Documentación\PETSc”, tienen lugar diversas discusiones sobre la utilización de Fortran con PETSc.

71

Page 9: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

‘PetscOptionsGetInt’ ‘PetscOptionsGetReal’ ‘PetscOptionsGetString’ ‘PetscOptionsGetStringArray’ ‘PetscOptionsGetIntArray’ ‘PetscOptionsGetRealArray’

Si es necesario utilizar alguna de estas funciones, se aconseja mirar su interfaz en el manual HTML de PETSc ya comentado en apartados anteriores.

Se aprovecha este momento para indicar que en PETSc, al igual que ocurría con MPI, hay unos tipos de variables definidos, que puede que no coincidan con los tipos de variables análogos de los lenguajes C o Fortran. Se pueden seguir utilizando los tipos de C o Fortran para escribir código, pero a la hora de llamar a las rutinas PETSc los argumentos han de ser de los tipos definidos por la librería, tales como PetscInt para enteros, PetscReal para números reales, PetscScalar para números reales de doble precisión, etc. No obstante, hay casos como el de PetscScalar, que se corresponde con un real de doble precisión de C/C++. Así mismo, PETSc proporciona rutinas tales como ‘PetscPrintf’, análoga a la función ‘printf’ de C, con la diferencia de que la primera reconoce el formato de los datos de PETSc, y la segunda no. Esto es importante tenerlo en cuenta a la hora de implementar códigos de aplicación, pero estas funciones no se utilizan con frecuencia.

3.3.3.3. PROFILING.

El término “profile” en términos informáticos, hace referencia al fichero de control de un programa, especialmente un fichero de texto leído automáticamente desde el directorio local de cada usuario, y pensado para ser fácilmente modificado por el usuario en orden a personalizar el comportamiento del programa. También se refiere a la acción de realizar un informe sobre las cantidades de tiempo consumidas por cada rutina, de forma que se puedan determinar los ajustes requeridos por el programa.

PETSc permite el “profiling” de programas de forma fácil y sencilla. Las rutinas registran las actividades realizadas si se especifican ciertas opciones en tiempo de ejecución. Además, PETSc proporciona un mecanismo para imprimir información sobre operaciones de computación.

Las opciones de profiling pasadas al programa como argumentos en tiempo de ejecución son las siguientes:

-log_summary: imprime información sobre el funcionamiento del programa al término del mismo. Esta opción ha sido muy utilizada en la fase de pruebas del proyecto, y ha sido de gran ayuda a la hora de determinar el tiempo de ejecución del programa, los parámetros utilizados, etc.

-info [infofile]: imprime información verbosa sobre el código a la salida estándar o a un fichero opcional. Esta opción proporciona detalles sobre algoritmos, estructuras de datos, etc. Esta opción, a diferencia de la anterior, supone una sobrecarga significativa, de modo que es mejor no utilizarla si lo que se quiere ver es el funcionamiento de la aplicación.

72

Page 10: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

-log_trace [logfile]: traza el comienzo y el final de todos los eventos PETSc. Esta opción, que puede ser utilizada en combinación con la anterior, es útil para ver dónde se queda colgado un programa, sin acudir al depurador.

Hay un paquete software denominado MPE (Multi-Processing Environment), que forma parte de la implementación MPICH de MPI. El Entorno Multi-Procesamiento (MPE), pretende proporcionar a los programadores un conjunto completo de herramientas para el análisis del funcionamiento de sus programas MPI. Estas herramientas incluyen: un conjunto de librerías de profiling, un conjunto de programas de utilidad y un conjunto de herramientas gráficas.

Las librerías de profiling proporcionan una colección de rutinas que crean ficheros de log (de registro). Estos ficheros de log se pueden crear manualmente insertando llamadas MPE en el programa MPI o automáticamente, enlazando la aplicación con la librerías MPE requeridas. También es posible combinar ambos métodos.

PETSc utiliza la opción en tiempo de ejecución ‘–log_mpe’ para crear un fichero de registro de eventos, apropiado para ser visualizado por Jumpshot, que es programa incluido con MPE para visualizar ficheros de log. La forma de indicar esta opción a PETSc en tiempo de ejecución es:

-log_mpe [logfile]

NOTA: En el apartado 12.1.3, del manual de usuario de PETSc, se habla un poco más sobre el registro de eventos particulares.

PETSc registra automáticamente la creación de objetos, tiempos y contadores de punto flotante para las rutinas propias de las librerías. El usuario, por su parte, puede complementaresta información monitorizando sus códigos de aplicación. Las partes del código a ser monitorizadas son los denominados eventos.

Las rutinas que utiliza PETSc para el registro de información (logging), se llaman“PetscLogXxxx”, y en particular, las rutinas que empiezan por “PetscLogEventXxxx” sededican al registro de eventos. Para obtener información sobre la interfaz y el manejo de estas rutinas se aconseja dirigirse al ya referenciado manual HTML de rutinas PETSc.

3.3.3.4. PETSC CON UN SUBCONJUNTO DE PROCESOS.

Hay veces las aplicaciones paralelas son muy grandes, y se requiere dividir el trabajo enpartes, asignando alguna parte a un subconjunto de procesos, y no a todos los procesos en su totalidad. Otras veces, no esta la razón de utilizar un subconjunto de procesos, sino que nos puede interesar que un proceso maestro coordine el trabajo de otros procesos esclavos a él.

Para lograr todo esto, habría que especificar un comunicador alternativo a‘PETSC_COMM_WORLD’. La forma de hacerlo es mediante la rutina siguiente:

PetscSetCommWorld(MPI_Comm comm)

A esta rutina hay que llamarla antes que a ‘PetscInitialize’, pero después de ‘MPI_Init’.No es frecuente el uso de esta rutina, ya que en vez de hacer esto, una buena alternativa seríallamar varias veces a ‘mpiexec’ de MPI (ver ANEXO I: mpiexec se encarga de ejecutar

NOTA: En el apartado 12.1.3, del manual de usuario de PETSc, se habla un pocomás sobre el registro de eventos particulares.

73

Page 11: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

programas en paralelo), y a cada llamada se le pasa un conjunto disjunto de procesos. Así sería fácil dividir el trabajo.

3.3.4. ESCRITURA, COMPILACIÓN Y EJECUCIÓN DE PROGRAMAS PETSC.

Las herramientas utilizadas a la hora de crear aplicaciones paralelas son diversas. En nuestro caso, se ha escogido el editor emacs para la escritura del código fuente. Esta elección se debe simplemente a la familiarización con el editor. Los compiladores escogidos, más que nada por compatibilidad con las librerías, han sido gcc para compilar código escrito en C, g++para código C++, y g95 para Fortran. Escoger los compiladores no fue tarea fácil, ya que al principio hubo problemas de compatibilidad de otros compiladores con las librerías PETSc y MPI.

Una vez escritos y compilados los programas, se ejecutan mediante los scripts de ejecución paralela que proporciona la librería MPI. En concreto, el script mpiexec es el encargado de realizar tal operación.

NOTA: En el capítulo 7 se habla un poco más sobre este tema.

La compilación no se lleva a cabo llamando directamente a los compiladores, sería un poco descabellado, ya que hay que indicar varios nombres de librerías que se utilizan con frecuencia: PETSc, MPI, BLAS, LAPACK y las propias de la aplicación. Hay programas muy cómodos de utilizar como el Fortran Visual de Compaq, que automatizan el proceso de compilación y el posterior enlazado de librerías; sin embargo, a la hora de instalar MPI o PETSc no era viable utilizar este tipo de herramientas.

Las fases de compilación y enlazado de código PETSc / MPI se realizan con la herramienta make, incluida dentro del paquete de Cygwin. Este comando hace uso de los denominados “makefiles”. El uso de esta herramienta facilita en gran medida el mantenimiento y depurado de programas.

Para comprender la forma de utilizar los makefiles con PETSc, lo mejor es ver un ejemplo, el cual se presenta a continuación.

CFLAGS = FFLAGS = CPPFLAGS = FPPFLAGS = LOCDIR = /home/josea/pruebas-petsc/pruebas_fortran EXAMPLESC =EXAMPLESF = struct.F90 diag_dom.F90 MANSEC = KSP

include ${PETSC_DIR}/bmake/common/base

struct: struct.o chkopts -${FLINKER} -o struct struct.o ${PETSC_MAT_LIB} ${RM} struct.o

diag_dom: diag_dom.o chkopts -${FLINKER} -o diag_dom diag_dom.o ${PETSC_KSP_LIB} ${RM} diag_dom.o

NOTA: En el capítulo 7 se habla un poco más sobre este tema.

74

Page 12: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

cl: rm -f *.*~ rm -f *~ rm -f \#*

En el código del makefile, vemos primeramente algunas variables bandera (flags), las cuales determinan cómo será compilado el código; de entre ellas la más importante es “LOCDIR”, que determina el directorio local donde se encuentran los ficheros de código fuente a compilar y enlazar.

A continuación, viene una sentencia ‘include’ muy importante, puesto que es la incluye otros makefiles, que son los que proporcionan las definiciones y reglas necesarias para la instalación PETSc básica para una arquitectura de sistema común. En estos makefiles se definen las variables que posteriormente el usuario utilizará para sus propios makefiles, como es el caso aquí mostrado. Estas variables son, por ejemplo, “FLINKER”, que define el linkador Fortran que se utilizará, “PETSC_MAT_LIB”, que define la ruta donde se encuentra el paquete de matrices de PETSc, y muchas otras.

El último bloque de sentencias se utiliza para eliminar los ficheros intermedios creados en la fase de edición de código.

En este makefile de ejemplo presentado, podemos observar que se definen dos objetivos para compilar dos ficheros: “struct.F90” y “diag_dom.F90”. La forma de invocar al comando make para compilar y enlazar estos ficheros con las librerías de PETSc es simplemente la siguiente: nos colocamos en el directorio donde se encuentra el makefile y ejecutamos las siguientes sentencias:

$ make struct$ make diag_dom

La primera instrucción compila el fichero struct.F90” y la segunda el fichero“diag_dom.F90”. Notar que los argumentos que se le pasan al comando make son los objetivos definidos en el makefile, no el nombre de los ficheros.

Por otro lado, si el makefile creado para compilar nuestras aplicaciones no tiene el nombre “makefile”, habrá que indicárselo a make con la opción –f nombre_makefile. Para aprender más opciones sobre las formas de invocar make, se puede acudir al manual proporcionado con el paquete Cygwin, al cual se accede mediante la siguiente sentencia ejecutada en una consola de Cygwin:

$ man make

La mejor forma de aprender a utilizar estos makefiles, es viendo los ejemplos que se incluyen con la instalación de PETSc, o viendo los creados para las pruebas realizadas para el proyecto. No obstante, en el capítulo 15 del manual de usuario de PETSc se habla algo más sobre la construcción de estos ficheros de apoyo.

75

Page 13: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.4. VECTORES.

3.4.1. INTRODUCCIÓN.

Hasta ahora, lo que hemos visto a sido una descripción del entorno donde se desarrollan las aplicaciones PETSc, así como algunas características propias de la librería. Las siguientes grandes secciones, incluyendo la actual, se dedican a explicar en profundidad los principales módulos software que proporciona PETSc, y que han sido utilizados en la realización de aplicaciones paralelas para el Cálculo de Estructuras. Esta primera sección se dedica al tema de vectores, primer paquete de Álgebra lineal, que será la base para el entendimiento de las siguientes librerías que se explicarán.

Ya dijimos que PETSc utiliza la filosofía de la Programación Orientada a Objetos (OOP), aun cuando C o Fortran no se ajustan a esta forma de programación (C++ sí, que también está soportado por PETSc). Pues bien, el objeto fundamental en la jerarquía de objetos de PETSc es el vector, denominado ‘Vec’ en términos de la librería. Los vectores se utilizan para almacenar soluciones de problemas, la parte del lado derecho del sistema Ax=b, etc.

En los siguientes apartados, veremos todo lo referente al manejo de este tipo de objetos, así como algunos paquetes software relacionados con los vectores, como son los índices o los arrays distribuidos.

3.4.2. CREACIÓN DE VECTORES.

PETSc nos da la posibilidad de crear dos tipos de vectores, en función de nuestras necesidades y de si el sistema es mono o multi-procesador. Los dos tipos son:

Vector secuencial: todas sus componentes (campos) pertenecen al mismo procesador. Vector paralelo: sus componentes se encuentran distribuidas a través de un conjunto

de procesadores. El procedimiento de distribución de componentes sobre los distintos nodos del cluster, así como las distintas operaciones que se llevan a cabo sobre el vector, se realizan mediante MPI. Por esta razón, al vector paralelo también se le denomina vector MPI.

PETSc soporta objetos con estructura de datos neutral, lo que permite un modelo de memoria distribuida, nada compartido, dentro de sistemas de procesadores individuales.

El objeto “vector” de PETSc es un indicador (puntero) a las entradas del vector real; esto permite que el vector paralelo se pueda distribuir a través de muchos procesos, pero tiene como contrapartida que no se puede acceder al vector de forma directa, sino a través de unas rutinas específicas que esconden un complejo de llamadas a MPI. PETSc permite trabajar con el vector global, no sólo con la parte local del vector que pertenece a un proceso particular, pero de nuevo, a través de un conjunto de operaciones que especifica la librería paralela. Por otro lado, el acceder al vector a través unas rutinas definidas por PETSc ayuda a no preocuparse de la complejidad de las operaciones de comunicación entre procesos, que siempre es un tanto compleja.

La creación de un vector se compone de tres fases:

76

Page 14: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

1. La creación del vector, propiamente dicha, donde cada proceso crea su vector particular, en el caso de vectores secuenciales, o donde cada proceso crea su parte local del vector paralelo, en el caso de vectores MPI. Los vectores paralelos (MPI) están formados por vectores secuenciales, y cada uno pertenece a un proceso, de modo que el vector global paralelo es la unión de todos estos vectores locales a los procesos.

2. La inserción de valores. Tras la creación del objeto, cada proceso inserta valores en las partes del vector que considere necesarias, ya sean partes locales o no locales al proceso.

3. El ensamblado del vector. Esta es la fase que tiene lugar tras la inserción de valores en el vector. Aquí se realiza todo el paso de mensajes de las componentes del vector no-locales a los procesos.

NOTA: No perder de vista que trabajamos con un modelo de programación paralela SPMD (Single Program, Multiple Data), es decir, todos los procesos ejecutan el mismo programa, aunque cada uno tiene su propios valores para las mismas variables, lo que da lugar a resultados distintos en cada nodo. Esto quiere decir que todos los procesos llaman a las mismas operaciones, y en el mismo orden; de modo que, en el caso particular de vectores, al crear, por ejemplo, un vector paralelo, todos los procesos lo crearán a la vez, y todos contribuirán con su parte local al vector MPI global.

La definición de las interfaces de todas las rutinas se encuentran perfectamente detalladas en el manual HTML de PETSc, al que de nuevo se aconseja acudir cada vez que se requiera utilizar cualquier rutina.

Las rutinas que proporciona PETSc para la creación de vectores son ‘VecCreateSeq’ y‘VecCreateMPI’ para el caso de vectores secuenciales y paralelos, respectivamente. Ambas rutinas requieren el nombre de un comunicador como argumento, que en el caso de ‘VecCreateSeq’ es ‘PETSC_COMM_SELF’, como no podría ser de otra manera. En el caso de la rutina para la creación de vectores MPI, se especifican dos dimensiones del vector, la local y la global. El usuario puede especificar ambas o una de las dos y la que resta dejar que la determine PETSc, indicando que dicha dimensión tenga el valor “PETSC_DECIDE”.

De forma equivalente, se pueden sustituir las dos rutinas anteriores por el conjunto de las tres siguientes: ‘VecCreate’, para crear el vector y especificar el comunicador utilizado, ‘VecSetSizes’, para especificar las dimensiones (local y global) del vector, y ‘VecSetFromOptions’, para coger de la base de datos la opción que indica el tipo de vector a utilizar. Si en línea de comandos se indicó el argumento “-vec_type mpi”, el vector que determinará la rutina ‘VecSetFromOptions’ será de tipo MPI, incluso en el caso uniproceso.

A la creación de un vector le sigue la inserción de valores en el mismo. Esta fase se lleva a cabo mediante la rutina ‘VecSet’, para la inserción de un valor simple a todas las componentes del vector, o mediante ‘VecSetValues’, para la inserción de un conjunto de valores. Si la opción escogida es esta última, se puede llamar varias veces a dicha rutina en cualquiera o en todos los procesos, pero la fase de inserción no está completa. Por medio de la rutina ‘VecSetValues’, cualquier proceso puede insertar valores en cualquier componente del

NOTA: No perder de vista que trabajamos con un modelo de programación paralelaSPMD (Single Program, Multiple Data), es decir, todos los procesos ejecutanel mismo programa, aunque cada uno tiene su propios valores para lasmismas variables, lo que da lugar a resultados distintos en cada nodo. Estoquiere decir que todos los procesos llaman a las mismas operaciones, y en elmismo orden; de modo que, en el caso particular de vectores, al crear, por ejemplo, un vector paralelo, todos los procesos lo crearán a la vez, y todoscontribuirán con su parte local al vector MPI global.

La definición de las interfaces de todas las rutinas se encuentran perfectamente detalladas en el manual HTML de PETSc, al que de nuevo seaconseja acudir cada vez que se requiera utilizar cualquier rutina.

77

Page 15: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

vector global, aun siendo componentes no locales. Por esta razón, es necesario pasar a la tercera fase, la fase de ensamblado (si se utiliza la rutina ‘VecSet’, el ensamblado se puede omitir). Decir que PETSc no proporciona ninguna rutina llamada “VecGetValues”, sino que proporciona métodos alternativos tales como el acceso a un array local, copia del vector paralelo, u operaciones de dispersión (scatter) de valores entre procesos (rutinas basadas en MPI pero con una interfaz PETSc, que esconde la complejidad de las comunicaciones MPI).

El ensamblado de un vector paralelo consiste en realizar todas las operaciones de comunicación interprocesos necesarias para distribuir los valores no locales previamente insertados. Las rutinas a las que hay que invocar para realizar el ensamblado son ‘VecAssemblyBegin’ y ‘VecAssemblyEnd’, en medio de las cuales se pueden realizar operaciones de computación, de modo que se aproveche el tiempo en que se realizan las comunicaciones pertinentes. Si un proceso inserta valores en su parte local del vector paralelo, no es necesario realizar la fase de ensamblado, puesto que no hay que migrar valores a otros procesos.

Uno de los parámetros a indicar en la llamada a la rutina ‘VecSetValues’ es si se desea insertar o añadir (sumar) valores a las componentes del vector en cuestión. En el caso de la inserción, se indica mediante el valor ‘INSERT_VALUES’, y en el caso de la adición el valor es ‘ADD_VALUES’. Es importante hacer notar que la inserción y la adición no se pueden combinar. Para poder mezclar ambas acciones, serían necesarias fases distintas de inserción y adición, con llamadas entre medio a las rutinas de ensamblado. Esta forma de trabajar elimina la incertidumbre del caso en que un proceso inserte y otro añada valores a las mismas localizaciones dentro del vector.

Una vez ensamblado el vector paralelo, un proceso puede obtener el tamaño de dicho vector llamando a ‘VecGetSize’. A su vez, si un proceso desea determinar el tamaño local de la parte que le corresponde del vector global, puede hacerlo mediante ‘VecGetLocalSize’.

Es lógico querer visualizar los nuevos objetos de tipo vector creados. La librería PETSc, dispone de un módulo software para la creación de visualizadores (viewers), que son objetos utilizados para examinar, imprimir y guardar otros objetos. Existen diversas rutinas para trabajar con visualizadores, algunas de las cuales se presentan en la sección 14.3 del manual de usuario de PETSc. Incluso hay algunas para interactuar con MATLAB.

En el caso concreto de vectores, la rutina ‘VecView’ muestra en pantalla las componentes del vector pasado como argumento (de nuevo, se insta acudir al manual HTML de funciones PETSc).

La creación de vectores está bien, pero si queremos obtener vectores con el mismo formato que el de uno recién creado, muchas veces interesaría más duplicarlos, así no habría que repetir las fases de creación, inserción de valores y ensamblado. Pues bien, PETSc suministra dos rutinas, ‘VecDuplicate’ y ‘VecDuplicateVecs’, para el duplicado de un vector en otro o en otros, respectivamente. La gran ventaja de introducir sentencias de duplicado de vectores en el código fuente, es que dicho código no depende del formato de los vectores utilizados.

Otras veces, por motivos implicados de una aplicación particular, puede ser útil crear vectores a partir de un array proporcionado por el usuario, cosa que se puede hacer con las rutinas ‘VecCreateSeqWithArray’ y ‘VecCreateMPIWithArray’. En este caso, no se deja a PETSc la opción de alojar en memoria el espacio para el vector, sino que lo hace el propio usuario; esto implica que es el propio programador el encargado de especificar la dimensión

78

Page 16: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

local del vector, teniendo cuidado de que el array sea lo suficientemente grande para contener los valores del vector local.

Tras la utilización de un vector, la única salida que tiene, como objeto que es, es ser destruido. Esta operación de eliminación se lleva a cabo mediante ‘VecDestroy’, para un solo vector, o con ‘VecDestroyVecs’, para realizar lo mismo con un array de vectores.

3.4.3. OPERACIONES FUNDAMENTALES CON VECTORES.

Algunas de las operaciones básicas con vectores proporcionadas por PETSc se presentan en la tabla 6, sacada del manual de usuario de PETSc. En la rutina ‘VecNorm’, el tipo de la norma del vector puede ser 1, 2 o infinito.

RUTINA OPERACIÓN REALIZADAVecAXPY y = y + a · x VecAYPX y = x + a · y VecWAXPY w = a · x + y VecAXPBY y = a · x + b · y VecScale x = a · x VecDot r = x*' · y VecTDot r = x' · y VecNorm r = ||x||tipoVecSum r = xi

VecCopy y = x VecSwap y = x mientras x = y VecPointwiseMult wi = xi · yi

VecPointwiseDivide wi = xi / yi

VecMDot r[i] = x*' · y[i] VecMTDot r[i] = x' · y[i] VecMAXPY y = y + i ai · x[i] VecMax r = max xi

VecMin r = min xi

VecAbs xi = |xi|VecReciprocal xi = 1/xi

VecShift xi = s + xi

VecSet xi =

Tabla 6. Operaciones básicas con vectores.

La tabla anterior es auto explicativa, y si se desea realizar alguna de estas operaciones, no hay más que acudir al manual HTML de funciones para ver la interfaz y utilizarla. Siempre se aconseja ir al manual HTML porque, de la experiencia adquirida programando con PETSc, se ha llegado a la conclusión de que es el único sitio donde las interfaces de las rutinas están correctamente indicadas, sin errores.

Hay una rutina muy importante y muy utilizada en las pruebas: ‘VecGetOwnershipRange’. La llamada a dicha rutina permite a cada proceso particular obtener, de un vector paralelo distribuido a través de todos los procesos, el rango local que le pertenece. El rango local se especifica con un índice inicial y un índice final, que

79

Page 17: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

corresponden, respectivamente, a la primera y la última componente del vector global que pertenecen al proceso que invoca la rutina. Es importante hacer énfasis en el hecho de que los índices devueltos por esta rutina son índices globales, que indican la parte del vector paralelo que pertenece al proceso local que llama a la rutina. En la siguiente sección, se verán métodos adicionales para tratar con índices. La rutina ‘VecGerOwnershipRange’ se suele utilizar en conjunción con ‘VecSetValues’, que también hace uso de índices globales.

Es comprensible que sea un poco complicado asimilar todos estos conceptos de índices y rangos locales y globales, vector paralelo y secuencial, comunicador colectivo o individual, etc.; todos estos conceptos seguramente se aclararán y asentarán viendo los códigos de las pruebas realizadas. Una gran ventaja de PETSc, es que proporciona un código muy legible, debido a los nombres asignados a las rutinas, por lo que es fácil e intuitivo seguir el código fuente de las aplicaciones, que, a su vez, es la mejor forma de aprender a programar con PETSc (además de ir mirando el manual HTML de funciones).

Ya dijimos que no había una rutina análoga a ‘VecSetValues’ que extrajese valores de los vectores, cosa que muchas veces es necesaria. Para tal fin, PETSc proporciona dos rutinas: ‘VecGetArray’, que devuelve un puntero a los elementos locales (del vector global completo) que pertenecen al proceso llamante; y ‘VecRestoreArray’, que elimina el puntero cuando ya no es necesario acceder más al vector local. El array (puntero al array) devuelto, no es una copia del vector local, sino que es un acceso directo a los elementos del vector (la parte del vector global que pertenece a un proceso particular, se encuentran almacenada localmente). Esta forma de trabajar da lugar a un aumento de la eficiencia, ya que no hay que crear ni destruir copias de datos.

Por último, puntualizar que hay rutinas con los nombres ‘VecxxxBegin’ y ‘VecxxxEnd’que aumentan la eficiencia de las comunicaciones. Este tipo de rutinas aprovechan el hecho de que se vayan a realizar varias operaciones consecutivas que requieran comunicaciones y así unificarlas, manteniendo los cálculos implicados por separado. Sin embargo, al estar basadas en MPI-1, no están preparadas para solapar cálculos con comunicaciones, lo que se espera conseguir en un futuro al mejorar estas rutinas con MPI-2.

Hasta aquí, la parte básica del paquete de vectores. En lo que resta de la sección, veremos algunos complementos del mismo paquete software, que servirán para facilitar la labor del programador.

3.4.4. ORDENACIÓN E INDEXADO (NUMERACIÓN).

En muchos problemas científicos, se requiere un procesamiento previo de la información antes de pasar a la fase de computación. Este procesado hace referencia a cómo se ordenan y numeran los diversos elementos que componen un problema, que es la base para empezar a realizar operaciones sobre estos datos. Existen diversas situaciones en las que esto ha de tener lugar, tales como códigos paralelos de PDE’s, en los que aparecen muchísimos vértices y grados de libertad. En nuestro caso, antes de realizar cálculos sobre una estructura concreta, es necesario realizar antes un mallado de la misma, y asignar a cada nodo resultante una identificación, para poder distinguir unos de otros. Tras realizar esto, es necesario numerarlos y ordenarlos de la forma que más convenga para nuestra aplicación.

80

Page 18: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

A todo esto hay que añadir lo siguiente: como bien sabemos, los datos se distribuirán a través de los nodos del cluster de ordenadores. Pues bien, cada proceso tendrá su propia numeración local de los objetos de datos, frente a la numeración global del conjunto.

Para toda esta casuística, PETSc suministra dos herramientas básicas, traducidas a paquetes software distintos: AO (Application Ordering – Ordenación de la Aplicación) e IS (Index Set – Batería de Indíces). AO permite mapear entre diferentes esquemas de numeración global, por ejemplo, entre la ordenación que utiliza PETSc y la que desearía utilizar un usuario para su aplicación; IS permite mapear entre numeración local (un proceso particular) y global (el conjunto de procesos).

A continuación, veremos las dos herramientas por separado, las cuales trabajan juntas en un punto, el cual veremos.

3.4.4.1. ORDENACIONES DE UNA APLICACIÓN.

Muchas veces es necesario, otras veces deseable, mantener diferentes formas de ordenar los elementos de un problema. Esto es una tarea complicada, debido a que es difícil que cada proceso mantenga información sobre el mapeado entre las distintas ordenaciones. En la figura 26, se presenta un ejemplo de lo comentado: tenemos una aplicación que mantiene una ordenación (natural) determinada para un array de 2D, mientras que PETSc utiliza una ordenación completamente distinta, que corresponde a la utilizada por las rutinas del Álgebra lineal. Cada bloque de datos pertenece a un proceso, los cuales utilizan una de las dos ordenaciones, según se le indique.

Figura 26. Ordenaciones distintas para un mismo array de datos en 2D.

Para crear una nueva ordenación de aplicación, basta con llamar a la rutina ‘AOCreateBasic’, la cual acepta dos arrays como argumentos, uno para especificar los valores de la ordenación que utiliza la aplicación, y otro para los valores de la ordenación que utiliza PETSc. Una vez creado el objeto AO, se pueden utilizar las rutinas ‘AOPetscToApplication’ y ‘AOApplicationToPetsc’ para mapear de una ordenación a otra. Si un conjunto de procesos llamó a la rutina ‘AOCreateBasic’, el mismo conjunto (no un subconjunto) ha de llamar a estas dos últimas rutinas, en el caso de que sea necesario, ya que la base datos que mantiene el mapeado entre ordenaciones es paralela, de modo que todos los procesos que la crearon han de mantenerla.

La rutina ‘AODestroy’ elimina el contexto de ordenaciones creado. Así mismo, ‘AOView’ visualiza el objeto AO creado por las rutinas pertinentes.

81

Page 19: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.4.4.2. CONJUNTOS DE ÍNDICES.

Un conjunto de índices (IS) es una generalización de un conjunto de enteros, utilizado para definir dispersiones, recolecciones y operaciones similares sobre vectores y matrices. La rutina ‘ISCreateGeneral’, crea un conjunto de índices a partir de un array de enteros. Hay variantes de esta rutina, como es el caso de ‘ISCreateStride’, que crea un conjunto de índices que contiene una lista de enteros igualmente espaciados por un paso (stride), o como ‘ISCreateBlock’, que crea un conjunto de índices para un conjunto de bloques de enteros (mirar el manual HTML de funciones). Cuando no se necesita más un conjunto de índices, se puede destruir mediante la rutina ‘ISDestroy’.

Hay una rutina, llamada ‘AOCreateBasicIS’, que crea una ordenación para una aplicación, no a partir de dos arrays de enteros (como así lo hacía la rutina ‘AOCreateBasic’), sino a partir de dos conjuntos de índices. Posteriormente, de forma análoga a como ocurre con ‘AOCreateBasic’, se puede llamar a las rutinas ‘AOPetscToApplicationIS’ y‘AOApplicationToPetscIS’ para mapear objetos IS entre las ordenaciones de aplicación y de PETSc.

Con los objetos AO, se busca hallar un camino para mantener diferentes ordenaciones para los mismos datos de un problema. Por otro lado, con los objetos IS se busca acceder localmente a los recursos globales y viceversa. PETSc proporciona unas rutinas para mapear los índices desde un esquema de numeración local (el de un proceso individual) al esquema de numeración global de PETSc, y viceversa. Con las rutinas ‘ISLocalToGlobalMappingCreate’ y ‘ISLocalToGlobalMappingDestroy’ se crea y se destruye, respectivamente, una estructura de índices para mapear entre una numeración local y otra global. Tras la creación, las rutinas ‘ISGlobalToLocalMappingApply’ y ‘ISLocalToGlobalMappingApply’ realizan el mapeado entre índices globales y locales. En el caso de un vector paralelo, esto es muy interesante, ya que ofrece la posibilidad de insertar valores con una numeración local para el proceso llamante, sin preocuparse por la numeración global del vector paralelo completo. Ya vimos que la rutina ‘VecSetValues’ utilizaba una numeración global para insertar valores en un vector paralelo. Ahora, haciendo uso de los conjuntos de índices, se pueden insertar valores mediante una numeración local. En concreto, la rutina ‘VecSetLocalToGlobalMapping’ llamada tras ‘ISLocalToGlobalMappingCreate’, mapea los índices locales a índices globales para acceder al vector, de modo que mediante la rutina ‘VecSetValuesLocal’ se puedan insertar valores utilizando índices locales, que automáticamente serán mapeados a los correspondientes índices globales, accediendo así al vector paralelo de forma correcta. Los índices locales seguirán una numeración propia de cada proceso, es decir, pertenecerán al rango de valores [0, size), donde size es el tamaño del vector local perteneciente al proceso (parte local del vector paralelo alojada en dicho proceso).

3.4.5. DISEÑO DE DATOS PARALELOS Y VALORES DE CONTORNO (GHOST VALUES).

Una situación típica en términos científicos, es la resolución de PDE’s (ecuaciones en derivadas parciales). Gestionar bien el área de datos es la clave para obtener un buen rendimiento en las aplicaciones paralelas basadas en la resolución de este tipo de problemas. Respecto al área de datos, decir que se puede tratar como una malla (o rejilla) estructurada o no estructurada. PETSc proporciona dos tipos de objetos para tratar sendos casos: los arrays

82

Page 20: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

distribuidos, para rejillas estructuradas, y los “VecScatter” (dispersión de vectores), para rejillas no estructuradas.

Para evaluar las funciones que representan las PDE’s, se requiere, en cada parte del proceso de resolución, obtener el valor del vector solución intermedio, el cual se encuentra distribuido entre los procesos que ejecutan la aplicación paralela. Cada proceso necesita los valores locales del vector paralelo que le corresponden, así como los valores de contorno (ghost values), que se encuentran almacenados en los procesos adyacentes. Este concepto se muestra en la figura 27.

Figura 27. Concepto de valores ghost (valores de borde).

En la resolución paralela del tipo de problemas que venimos comentando (problemas derivados de PDE’s), intervienen cuatro puntos importantes a tener en cuenta:

1. Geometría de los datos: es importante identificar si los datos se presentan en forma de rejilla estructurada o no estructurada.

2. Creación de la estructura de soporte para los datos: tanto para los datos locales como para los valores ghost.

3. Actualización de valores ghost: cada proceso envía/recibe los correspondientes valores ghost a/de su vecino.

4. Fase de computación numérica local: cada proceso utiliza sus valores locales y los valores ghost para realizar las operaciones pertinentes.

Existen dos tipos de representaciones de los datos que almacena cada proceso: la representación global y la local. En la global, cada proceso almacena un único conjunto local de nodos de la rejilla, y cada nodo pertenece a un único proceso. En la representación local, cada proceso almacena un único conjunto local de nodos de la rejilla, así como un conjunto de nodos ghost que pertenecen a los procesos adyacentes. Estas dos formas de representación se muestran en la figura 28. Las representaciones son las ya denominadas ordenaciones, las cuales se pueden manejar mediante los objetos AO que proporciona PETSc.

A continuación, pasaremos a ver los métodos que utiliza PETSc para tratar con rejillas, que ya se han comentado: arrays distribuidos y ‘VecScatter’.

83

Page 21: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

Figura 28. Representación global y local de los nodos pertenecientes a un proceso.

3.4.5.1. REJILLAS ESTRUCTURADAS: ARRAYS DISTRIBUIDOS

Los arrays distribuidos (DA), son un paquete software de PETSc independiente al módulo de vectores, pero ambos funcionan en conjunto para trabajar con rejillas lógicas de datos regulares y rectangulares, a la hora de distribuir datos no locales entre los distintos procesos. Los arrays distribuidos de PETSc, están diseñados para el único caso en que se pueda pensar que los datos están almacenados en un array lógico multidimensional estándar. Por esta razón, los DA’s no están pensados para paralelizar rejillas de datos no estructuradas.

Los datos de la aplicación paralela se almacenan en objetos de tipo vector, de tamaño apropiado. Los objetos DA sólo contienen la información del esquema de datos paralelos y la información de la comunicación de valores ghost, aunque se pueden utilizar para crear vectores y matrices con un diseño apropiado.

Al trabajar con DA’s, el primer paso a realizar es la creación de estos objetos. Se puede crear una estructura de datos de comunicación de un DA mediante las rutinas ‘DACreate1d’,‘DACreate2d’ o ‘DACreate3d’, dependiendo de si la estructura de la rejilla estructurada regular es de 1D, 2D o 3D, respectivamente. Hay dos parámetros importantes a pasar como argumentos a estas rutinas: el número de puntos de rejilla y el número de procesos, ambos especificados para cada dirección. Otro parámetro a indicar es el tipo de estructura del array distribuido; los tipos disponibles son dos: estructura de tipo caja y de tipo estrella. Ambos se muestran en la figura 29. Esta definición del tipo de estructura del DA determinará los valores ghost que se utilizarán.

Ya hemos dicho que los objetos DA, sólo contienen información sobre la estructura de datos paralela y las comunicaciones. Los datos se almacenarán en vectores. Y este es el siguiente paso, crear los vectores que contengan la información sobre los datos (incluidos los valores ghost).

Figura 29. Patrón de la estructura de datos para los valores ghost.

84

Page 22: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

Cada objeto DA define el diseño de dos vectores: un vector global distribuido y un vector local que incluye espacio para los puntos ghost pertinentes. Cada proceso almacena una única porción del vector global, y cada proceso almacena su vector local completo más el alojamiento necesario para los valores ghost. El objeto DA informa sobre el tamaño y el esquema de sendos vectores, pero no aloja espacio para los valores de dichos vectores. La forma de trabajar es creando los dos vectores a partir de la información proporcionada por el objeto DA. Las rutinas que realizan esto son ‘DACreateGlobalVector’ y‘DACreateLocalVector’. A partir de que se crean los objetos vector, se pueden utilizar todas las rutinas del módulo de vectores.

Cuando dentro de una aplicación un proceso necesita los valores ghost para realizar ciertas operaciones, hemos de conseguirlos mediante operaciones de dispersión (scatter) de dichos valores desde los procesos adyacentes al proceso local. Esto es, hemos de realizar una dispersión de algunos valores del vector global al vector local. PETSc suministra dos rutinas para realizar la operación de dispersión en dos pasos: ‘DAGlobalToLocalBegin’ y ‘DAGlobalToLocalEnd’. En medio de estas dos rutinas, las cuales realizan un entramado de comunicaciones, se pueden realizar operaciones de computación, de modo que podamos aumentar el rendimiento de nuestra aplicación. Al término de dichas operaciones, se dispone del vector local con los valores ghost actualizados, momento en el que se pueden llevar a cabo las operaciones necesarias. Una vez terminado el procesado del vector local, se pueden enviar los nuevos valores de dicho vector al vector global mediante una operación de dispersión, la cual realiza la rutina ‘DALocalToGlobal’.

Por último, señalar que existen dos rutinas, ‘DaLocalToLocalBegin’ y‘DALocalToLocalEnd’, que se utilizan para copiar de un vector local que contiene puntos ghost no actualizados a otro vector local, el cual, ahora sí, contendrá los correspondientes valores ghost actualizados. La única cosa a tener en cuenta al realizar esta operación, es que ambos vectores locales han de tener el mismo diseño paralelo, es decir, han de ser compatibles con el mismo DA (generados por el mismo objeto DA, si cabe).

Realmente, de los dos vectores, el global y el local, se trabaja con el local a la hora de realizar operaciones de computación, puesto que es el que contiene los valores ghost. Ya hemos visto cómo crear ambos vectores, así como la forma de actualizar los valores ghost. Sin embargo, esta forma de actualizar los valores de contorno es poco eficiente. PETSc provee las siguientes rutinas: ‘DAGetLocalVector’ que, como su propio nombre indica, obtiene el vector local correspondiente (con los valores ghost correctos), tras lo cual se realizan las operaciones necesarias en el proceso actual; y ‘DARestoreLocalVector’, que libera el vector de trabajo local cuando ya no se necesita. Estas dos rutinas apenas requieren tiempo de CPU para su funcionamiento.

Tras realizar todas estas fases de creación de diseño paralelo de vectores mediante objetos DA, creación de los vectores correspondientes a los diseños realizados, etc., lo que resta es insertar valores en dichos vectores. Las rutinas destinadas a tal fin son dos, ‘DAVecGetArray’ para obtener un array en el que insertaremos los valores generados en nuestro programa de aplicación, y ‘DAVecRestoreArray’ para restaurar el array y mapear los valores al vector distribuido. A la primera rutina, se le puede especificar que el vector al cual queremos insertar valores sea el global o el local, pero una vez que obtengamos el array, sólo podremos acceder a los valores locales y a los valores ghost, el resto de valores estarán indefinidos. La forma de acceder al array es utilizando una indexación global, como si estuviéramos accediendo al vector global de forma directa (aunque sabemos que eso no es posible si no es a través de rutinas que utilizan arrays intermedios).

85

Page 23: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

Decir que existen otras rutinas, como ‘DAGetCorners’ y ‘DAGetGhostCorners’,empleadas para obtener información sobre la estructura de la rejilla datos, en concreto el valor de los índices de la esquina inferior izquierda de la parte local correspondiente al objeto DA en cuestión.

Como última aplicación de los arrays distribuidos, puntualizar que es posible mapear entre las ordenaciones de aplicación y de PETSc utilizadas para gestionar las rejillas regulares rectangulares. En su momento vimos que el paquete software de objetos AO era el encargado de gestionar el tema de las ordenaciones de datos. A su vez, también vimos cómo PETSc utiliza una ordenación de los datos distinta a la ordenación natural de la que hace uso una aplicación particular (apartado 3.4.4.1., figura 26). Pues existen rutinas, pertenecientes al módulo de objetos DA, que complementan su funcionamiento con las rutinas del módulo de objetos AO. Por ejemplo, la rutina ‘DAGetAO’ obtiene el contexto de ordenación de datos de la estructura paralela representada por un array distribuido particular.

3.4.5.2. REJILLAS NO ESTRUCTURADAS: VECSCATTER.

Establecer los patrones de comunicación en rejillas no estructuradas es mucho más complejo que en el caso de rejillas estructuradas, debido a la dependencia con la malla y con la discretización.

En el apartado 3.4.4.2., se hizo una breve descripción del paquete de conjuntos índices (IS). PETSc utiliza este tipo de objetos para facilitar las dispersiones y recolecciones (scatters & gathers) utilizadas para actualizar los puntos ghost de problemas definidos sobre rejillas de datos no estructuradas. Así mismo, es muy probable que sea necesario utilizar los objetos AO (Application Ordering) para mapear entre el esquema de ordenación de nuestra aplicación y el de PETSc, de modo que consigamos que los valores situados en procesos adyacentes sean numerados de forma secuencial (así facilitar el manejo de los valores ghost).

La librería paralela PETSc proporciona, dentro del módulo de vectores, soporte completo para las operaciones de dispersión y recolección. En términos de PETSc, se hace referencia a estas operaciones como dispersiones generalizadas, aunque en la actualidad son una combinación de dispersiones y recolecciones.

El programador puede seleccionar cualquier subconjunto de las componentes de un vector e insertarlas o añadirlas a cualquier subconjunto de las componentes de otro vector. Para tal fin, PETSc utiliza un término denominado contexto de dispersión, que es un objeto del tipo ‘VecScatter’, utilizado para referirse a un entorno de comunicaciones concreto. La razón de utilizar los contextos es que podemos diferenciar entre distintas operaciones de dispersión con vectores. La rutina ‘VecScatterCreate’ es la encargada de crear el contexto de dispersión, y hay que indicarle los vectores que intervendrán en la operación (pueden ser paralelos o secuenciales), así como el subconjunto de un vector que se copiará a otro subconjunto igual o distinto al del otro vector. El número de elementos de ambos subconjuntos ha de ser idéntico. ‘VecScatterDestroy’ se encarga de eliminar los datos creados por la rutina anterior.

La dispersión se lleva a cabo a través de ‘VecScatterBegin’ y ‘VecScatterEnd’, con una característica peculiar: una operación de dispersión puede realizarse a la inversa. Esto último se indica mediante un argumento pasado a estas dos rutinas, el cual puede tomar el valor ‘SCATTER_FORWARD’ (dispersión del vector global al local) o ‘SCATTER_REVERSE’(dispersión del vector local al global).

Las operaciones de dispersión son colectivas, lo que implica que todos los procesos que sean propietarios de un vector paralelo, sobre el que se realiza una dispersión, han de llamar a las rutinas de dispersión.

86

Page 24: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

Antes de pasar al siguiente punto, veamos dos características importantes de las dispersiones. La primera es que cualquier pareja de vectores que tenga el diseño paralelo conveniente (mismo número de elementos en cada proceso), puede utilizar el objeto VecScatter creado con la rutina ‘VecScatterCreate’. No importa que los vectores utilizados para la dispersión no hayan intervenido en la llamada a dicha rutina si, como ya se ha comentado, tienen el mismo esquema paralelo que el de los vectores utilizados en la llamada. Esto es una ventaja, ya que se puede reutilizar el contexto de dispersión, de modo que se reducen las comunicaciones y, por ende, aumenta el rendimiento de nuestra aplicación. La segunda característica a comentar, es que no existe una rutina que realice la función inversa a ‘VecSetValues’ (esto ya se ha apuntado en otra ocasión). Es decir, se pueden insertar valores en un vector paralelo con ‘VecSetValues’ y después ensamblar el vector con las rutinas correspondientes, pero no existe rutina alguna para obtener de la misma forma los valores. En su defecto, con PETSc se trabaja de forma que primero se realiza una dispersión de valores a un vector creado al efecto, y posteriormente se llama a la rutina ‘VecGetArray’ para mapear los valores del vector a un array pasado como argumento. Al final, es necesario llamar a ‘VecRestoreArray’ devolver al sistema el array empleado.

Al igual que ocurría en el caso de rejillas estructuradas, en rejillas no estructuradas también es importante el uso de valores ghost (valores que se encuentran en los procesos inmediatamente adyacentes al proceso local, y que son fundamentales para realizar diversas operaciones de computación). Sin embargo, la forma de trabajar es algo distinta al caso de rejillas estructuradas. El método consiste en dos etapas de dispersión. Primeramente, se realiza una dispersión de valores desde el vector paralelo a los vectores locales de trabajo, que contienen espacio para valores ghost (‘SCATTER_FORWARD’). A continuación se realiza la computación requerida utilizando los vectores locales. Por último, se lleva a cabo una dispersión hacia atrás para introducir el resultado en el vector solución global (‘SCATTER_REVERSE’).

Antes de terminar, veamos una última cuestión relacionada con el tratamiento que ofrece PETSc para rejillas no estructuradas. Todo lo que se ha hablado hasta ahora en este apartado presenta dos inconvenientes: es necesario memoria adicional para almacenar los vectores locales de trabajo (partes locales del vector global que incluyen espacio para los valores ghost), y además se necesita tiempo suplementario para copiar los valores del vector global a los vectores locales. PETSc tiene en cuenta estos inconvenientes, y proporciona rutinas para alojar espacio para los vectores globales con prealojamiento de memoria para los valores ghost. Estas rutinas son ‘VecCreateGhost’ y ‘VecCreateGhostWithArray’; a ambas se les pasa como argumento un array que contendrá las posiciones donde se encuentran los valores ghost dentro del vector global. La rutina ‘VecGhostGetLocalForm’ es la encargada de mapear el vector global paralelo al vector secuencial correspondiente, que incluirá los valores ghost. Este vector secuencial comparte el mismo espacio de memoria que el vector paralelo. Al vector MPI (paralelo) se accede mediante una numeración global y, al secuencial, con una numeración local (también para los valores ghost). La rutina complementaria a la anterior es ‘VecGhostRestoreLocalForm’, que produce el mismo efecto que el uso de ‘VecRestoreArray’ tras ‘VecGetArray’. En el caso de rejillas no estructuradas, las rutinas ‘VecGhostUpdateBegin’ y ‘VecGhostUpdateEnd’ cumplen la misma función que la que realizaban ‘VecScatterBegin’ y ‘VecScatterEnd’ en rejillas estructuradas, salvo por una salvedad, que en este caso no se necesitan copiar los valores locales al proceso, sólo los valores ghost, ya que los valores del vector local comparten el mismo array que los valores del vector global.

87

Page 25: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.5. MATRICES.

3.5.1. INTRODUCCIÓN.

Una vez visto el módulo de vectores, primer paquete de Álgebra Lineal proporcionado por PETSc y fundamental para entender los posteriores paquetes, pasemos a ver el módulo de matrices, segundo paquete de Álgebra Lineal. Las matrices son los objetos fundamentales que almacenan operadores lineales, como es el caso de los Jacobianos. A lo largo de esta sección, veremos cómo trata PETSc las matrices y qué posibilidades ofrece. La forma de gestionar los objetos de tipo matriz es la misma que en el caso de vectores: primero se crea una matriz, se insertan valores, se utiliza para varias operaciones de computación y, finalmente, se destruye. En este sentido, es útil pensar en matrices como conjunto de vectores, al menos en un principio, para así abordar el enrevesado tema de la paralelización.

PETSc proporciona alrededor de unos cincuenta formatos de matrices distintas, tanto para funcionamiento en sistemas uniprocesador como en paralelo. Sin embargo, los dos formatos principales son la matriz densa y la matriz de fila dispersa comprimida (compressed sparse row), también llamada matriz AIJ. Está última es la que más fomenta PETSc a la hora de realizar programas de aplicación, y así lo demuestra al realizar más rutinas que trabajan con este tipo de matrices que con otras. Una gran ventaja que proporciona PETSc, es que el código de las aplicaciones no depende del tipo de matrices utilizado.

3.5.2. CREACIÓN DE MATRICES.

Veamos ahora, de forma general sin particularizar el tipo de matriz empleado, las operaciones básicas para crear, ensamblar e insertar valores en una matriz genérica. Para tal fin, PETSc facilita una rutina denominada ‘MatCreate’, la cual crea un objeto de tipo matriz. Esta matriz será secuencial si el programa se ejecuta en un sistema uniprocesador o paralela si se ejecuta en un sistema multiproceso. El tipo de matriz se especifica en tiempo de ejecución a través de la ya comentada base de datos que mantiene el programa de aplicación con la rutina ‘MatSetFromOptions’, o lo especifica el propio programa a través de la rutina ‘MatSetType’. Los tipos de matrices disponibles más comunes son:

Matriz dispersa (AIJ): MPIAIJ (paralela), SEQAIJ (secuencial). Matriz dispersa en bloque: MPIBAIJ, SEQBAIJ. Matriz dispersa en bloque simétrica: MPISBAIJ, SEQSBAIJ. Matriz diagonal en bloque: MPIBDIAG, SEQBDIAG. Matriz densa: MPIDENSE, SEQDENSE. Matriz libre. Etcétera.

La razón de que haya tantos tipos de matrices distintos, se debe a que ninguna estructura de datos general es apropiada para todos los tipos de problemas. Comentar que los formatos de matrices en bloque y diagonales proporcionan beneficios de rendimiento significantes.

Además, aparte de la gran selección de formatos de matrices que proporciona PETSc, decir también que es relativamente fácil extender la librería añadiendo nuevas estructuras de datos (aunque esto aquí no lo veremos).

En el caso de matrices paralelas, al igual que ocurría con vectores, cada proceso aporta su parte de la nueva matriz MPI que se creará. PETSc particiona las matrices por filas no por

88

Page 26: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

columnas, esto es, cada proceso aporta una submatriz de un número cualquier de filas y de N columnas. Todos los procesos tienen las mismas columnas (partición por filas). Esto se aclara en la figura 30. La suma de las filas de cada proceso dará lugar a la dimensión (primera) global de la matriz total. La segunda dimensión es N. La descomposición en filas consecutivas a través de los procesos, en el caso de matrices dispersas (AIJ), es un procedimiento sencillo, y resulta más fácil trabajar con otros códigos de otras aplicaciones.

Supongamos por un momento, que no queremos descomponer nuestra matriz en conjunto de filas, sino en otro tipo de conjunto, por ejemplo, por grupos de columnas. Por otro lado, sabemos que PETSc siempre descompone en conjunto de filas. La pregunta es cómo hacer lo primero teniendo en cuenta lo segundo. Y la respuesta se puede obtener fácilmente si recordamos el paquete de objetos AO que proporciona PETSc. Estos objetos se encargan de mapear entre la ordenación de aplicación y la de PETSc. Si ordenamos los índices de la matriz de nuestra aplicación por columnas, y mapeamos a los índices que utiliza PETSc (ordenados por filas), tendremos resuelto el problema. Todo esto está muy bien si lo que buscamos es “dividir” nuestra matriz por columnas (no es dividir, es simplemente una vista de la matriz, como si estuviera descompuesta por columnas), porque así lo requiera nuestro problema. Sin embargo, esto presenta claros problemas de eficiencia, debido a que PETSc distribuye a través de los procesos los grupos de filas y, si ahora ordenamos por columnas, cada columna estará distribuida a través de todos los procesos, no son locales a los mismos. De modo que, al realizar cualquier cálculo local con una columna, sería necesario realizar varias operaciones de comunicación desde todos los procesos al proceso local para obtener la columna completa. Esto disminuye en gran medida el rendimiento, por eso no es aconsejable en absoluto.

Con la llamada a ‘MatSetSizes’,se especifican las dimensiones globales y locales de la matriz, esto es, cada proceso será poseedor de una parte de la matriz global, y cada parte de cada proceso tendrá sus propias dimensiones, cuyas sumas correspondientes serán las dimensiones de la matriz global. En general, el usuario especifica las dimensiones locales o las globales, no ambas. Esto hace que PETSc sea la que decida las dimensiones no especificadas por el usuario, de

Figura 30. Contribución de 3 procesos para modo que sea la librería la que formar una matriz paralela. controle el alojamiento de espacio

en memoria para la matriz. Esto se verá posteriormente con más detalle. ‘MatGetOwnershipRange’ se encarga de devolver las filas que pertenecen al proceso que llama a dicha rutina.

Una vez creada la matriz, tiene lugar la fase de inserción de valores, la cual se lleva a cabo mediante la rutina ‘MatSetValues’, accediendo a las posiciones de la matriz mediante una indexación global. Esta rutina inserta (INSERT_VALUES) o añade (ADD_VALUES) en la matriz un bloque de valores de dimensiones m x n. Ya se apuntó en su momento, que PETSc estaba escrita en C. Esto hace que, aunque escribamos código en Fortran, los índices para acceder a las filas y a las columnas de la matriz global empiezan por cero, que es la convención que sigue el lenguaje C (en Fortran empiezan por uno). Por otro lado, a la rutina indicada, se le pasa un array bidimensional que contiene los valores a insertar. Dicha rutina

89

Page 27: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

trata el array como si los valores estuvieran almacenados por filas (convención de C, opuesta al almacenamiento por columnas que utiliza Fortran); esto es, al insertar un valor en la posición (i,j), el elemento a obtener del array se encontrará en la posición [i * n + j], siendo ‘n’ el número de columnas del bloque de valores a insertar. No obstante, es posible indicarle a la rutina que los valores del array están almacenados por columnas a través de la rutina ‘MatSetOption’. Además, con esta rutina se puede especificar también si la matriz es simétrica, hermítica, etc.

Hay algunos formatos de matriz que almacenan los valores por bloques. En estos casos, es más eficiente llamar a la rutina ‘MatSetValuesBlocked’ que a ‘MatSetValues’. Esto último da lugar a la siguiente afirmación: una matriz es definida por su interfaz (las operaciones que se pueden realizar sobre ella), no por su estructura de datos.

Si nos acordamos de la fase que venía a continuación al trabajar con vectores, veremos que es la misma que viene ahora al trabajar con matrices: fase de ensamblado. Esta etapa es necesaria para colocar en sus lugares correspondientes los valores no locales a los procesos, ya que estos últimos pueden insertar valores en cualquier posición de la matriz global (aunque, por razones de eficiencia, se aconseja que cada proceso genere valores para las posiciones locales al mismo). Las rutinas de ensamblado pertinentes son ‘MatAssemblyBegin’ y ‘MatAssemblyEnd’. De nuevo, es posible realizar operaciones de computación en medio de las dos llamadas a las rutinas anteriores, de modo que podamos aumentar el rendimiento de nuestra aplicación. Como en el caso de vectores, no se pueden mezclar fases de inserción y adición de valores sin intercalar rutinas de ensamblado. Ante este escenario, PETSc permite en la fase de ensamblado final, pasar un argumento a las rutinas anteriores denominado ‘MAT_FINAL_ASSEMBLY’ y, durante las fases de ensamblado intermedias, un argumento llamado ‘MAT_FLUSH_ASSEMBLY’. Con esto se evita que se realicen ciertas operaciones del ensamblado total que se realiza en la fase final.

Respecto a lo anterior, decir que para las matrices dispersas, las filas son comprimidas una vez terminada la fase de ensamblado final. A partir de este momento se pueden realizar operaciones de multiplicación, transposición, etc., con las matrices. Una operación que no es aconsejable, es insertar valores una vez terminada la fase final de ensamblado, ya que esto requiere copias de valores y, posiblemente, alojamiento adicional de memoria (sólo almacenan los valores distintos de cero). No obstante, puede que nuestro problema necesite cambiar valores y, por tanto, requiera varias fases de ensamblado. En esta situación, si nuestra matriz mantiene las mismas posiciones de valores distintos de cero, es posible llamar a la rutina ‘MatSetOption’ con el valor “MAT_NO_NEW_NONZERO_LOCATIONS”. Así, se reutiliza la estructura de datos, con el fin de aumentar la eficiencia de nuestra aplicación. A esta última rutina, hay que invocarla después del ensamblado completo de la primera matriz.

En la introducción a esta sección, se expuso que los dos formatos principales de matrices que suministra PETSc son la matriz densa y la matriz de fila dispersa comprimida (o matriz AIJ). En los dos siguientes apartados, veremos ambos tipos por separado.

3.5.2.1. MATRICES DISPERSAS.

Las matrices dispersas (sparse), también llamadas matrices AIJ o CSR (fila dispersa comprimida), son el tipo de matriz que utiliza PETSc por defecto en sus códigos de aplicación. También es el tipo de matriz que se ha utilizado en las pruebas realizadas en el proyecto presente.

90

Page 28: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

En este tipo de matrices, PETSc almacena los elementos distintos de cero por filas, junto con un array indicando las posiciones (columnas) donde se encuentran los elementos dentro de una fila, así como un array de indicadores que apunten al comienzo de cada fila de la matriz.

La técnica utilizada, cuando es posible, para almacenar las matrices AIJ, tanto secuenciales como paralelas, es la de nodos-i (nodos idénticos). Este método se basa en asignar un número de nodo a cada fila de la matriz, y se almacenan sólo aquellas filas (nodos) que sean distintas a las demás. Filas idénticas tendrán el mismo número de nodo, pero sólo uno de ellos se alojará en memoria.

NOTA: En alguna ocasión, se ha comentado ya que Fortran utiliza la convención de indexar los arrays empezando por 1, mientras que C empieza por 0, que es la opción por defecto utilizada por PETSc (ya que la librería está escrita en C). Cada vez que se llama a una rutina PETSc, es inevitable utilizar la indexación empezando por 0. Sin embargo, hay veces que interesa utilizar paquetes externos a PETSc escritos en Fortran, y que utilizan la indexación comenzando por 1. Para tales situaciones, PETSc permite indicar por línea de comandos con la opción –mat_aij_oneindex, que las rutinas que no son de PETSc utilizan indexación basada en 1 para acceder a las matrices creadas por la librería paralela.

Al igual que en el caso de vectores, las matrices pueden ser secuenciales o paralelas. Veamos por separado ambos tipos, de los que se pueden esperar algunas características ya vistas en el tema de vectores; sin embargo, otras peculiaridades que se verán son propias de los objetos de tipo matriz.

MATRICES AIJ SECUENCIALES.

La rutina encargada de crear una matriz AIJ secuencial es ‘MatCreateSeqAIJ’. Esta rutina equivale a llamar a las rutinas ‘MatCreate’, ‘MatSetFromOptions’ (o ‘MatSetType’) y ‘MatSetSizes’ en conjunto y en este orden. A esta rutina se le puede especificar el número de elementos distintos de cero en cada fila de la matriz. Esto es una forma de realizar el prealojamiento de espacio en memoria para la matriz. PETSc también permite no especificar este argumento que indica los elementos no ceros, lo que implica que sea la librería la que realice el prealojamiento de espacio. Sin embargo, realizar prealojamiento dinámico de memoria, disminuye enormemente la eficiencia del programa, de modo que ha de ser el usuario quien realice semejante operación en grandes problemas de computación.

Como se acaba de comentar, PETSc permite especificar los elementos distintos de cero de la matriz. Para tal fin, la librería permite indicar los elementos no ceros para todas las filas (si todas tienen prácticamente el mismo número de no ceros) mediante un escalar o, por el contrario, pasar a la rutina un array de dimensión igual al número de filas de la matriz, que contenga los elementos no ceros para cada fila, aproximadamente. En ambos casos, cuanto más exactos seamos en la aproximación, menor número de prealojamientos dinámicos tendrá que hacer la librería y, en consecuencia, mayor rendimiento tendrá nuestra aplicación.

Si a priori no se conocen los elementos distintos de cero de la matriz, es usual realizar un pequeño trozo de código antes del prealojamiento de espacio de la matriz, para computar el número de elementos no cero. Esto requerirá una sobrecarga adicional, pero ni mucho menos

NOTA: En alguna ocasión, se ha comentado ya que Fortran utiliza la convención deaindexar los arrays empezando por 1, mientras que C empieza por 0, que es laopción por defecto utilizada por PETSc (ya que la librería está escrita en C).Cada vez que se llama a una rutina PETSc, es inevitable utilizar laindexación empezando por 0. Sin embargo, hay veces que interesa utilizar paquetes externos a PETSc escritos en Fortran, y que utilizan la indexacióncomenzando por 1. Para tales situaciones, PETSc permite indicar por línea decomandos con la opción –mat_aij_oneindex, que las rutinas que no son de PETSc utilizan indexación basada en 1 para acceder a las matrices creadas por la librería paralela.

91

Page 29: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

comparable a la necesaria en el caso de no realizar el prealojamiento por parte del usuario, dejando a la librería que sea la que lo realice de forma dinámica. Esta es la forma más eficiente de trabajar en aquellos casos en que nos dan una matriz, por ejemplo la correspondiente a una estructura, y hemos de realizar el prealojamiento de espacio en memoria para nuestra aplicación.

Una vez creada la matriz y realizado el prealojamiento de memoria (ambas fases se realizan mediante la misma subrutina), se insertan valores en las correspondientes posiciones (estos valores pueden ser generados por nuestra aplicación o recogidos de entidades externas, como puede ser un fichero). Tras la inserción de valores, se lleva a cabo la fase de ensamblado, explicada en la introducción del apartado de matrices.

MATRICES AIJ PARALELAS.

‘MatCreateMPIAIJ’ es la rutina proporcionada por PETSc para crear una matriz dispersa paralela. En este momento se requiere un poco de atención adicional, pues siempre los temas de “objetos paralelos” son un poco más complejos de entender.

En el caso de vectores, vimos que dichos objetos eran distribuidos a través de todos los procesos que intervenían en el proceso de computación paralela, cada proceso poseía una parte del vector. El número de filas (componentes) del vector que pertenecían a un proceso las podía especificar el usuario o, por el contrario, podía ser PETSc la que las repartiera de una forma más o menos equitativa entre los procesos.

Ahora, en el caso de las matrices, ocurre algo similar, sólo que en dos dimensiones en vez de en una. Esto complica un poco el esquema (mental) que previamente ha de realizarse antes de nada. Partimos de que PETSc distribuye las matrices por filas a través de los procesos, y esto es algo inamovible. Lo que sí puede indicar el usuario es el número de filas que pertenecen a un proceso particular. Y esta es la base de todo, aunque se puede exprimir bastante.

Pensemos que, en general, uno de los fines de nuestra aplicación será obtener el resultado de una matriz por un vector (el caso de matriz por matriz es fácilmente extensible), o la solución del sistema Ax=b. En cualquier caso, aparece una multiplicación de una matriz ‘A’ por un vector ‘x’. La segunda dimensión global de la matriz ‘A’ (número de columnas) ha de ser idéntica a la dimensión global del vector ‘x’. Respecto a esto, existe un problema, que la matriz y el vector se encuentran distribuidos a través del conjunto de procesadores implicados en la aplicación paralela. En este sentido, hemos de tener en cuenta que la segunda dimensiónlocal de la matriz (número de columnas locales) a de ser idéntica a la dimensión local del vector ‘x’ en cada proceso. Así mismo, en cada proceso, la primera dimensión local de la matriz (número de filas locales) ha de ser la misma que la dimensión local del vector solución ‘b’.

La rutina ‘MatCreateMPIAIJ’ permite que se le indiquen las dimensiones locales y globales de la matriz a crear. PETSc da la opción de que sea la misma librería la que especifique la dimensión global o la local (no ambas). La forma de hacerlo es dando el valor ‘PETSC_DECIDE’ a los argumentos de la dimensiones en cuestión (globales o locales). La dimensión que no especifique PETSc ha de indicarla obligatoriamente el usuario (el usuario también puede especificar las dos, aunque ha de tener en cuenta la coherencia que ha de tener lugar entre las dimensiones globales y la suma de las dimensiones locales de cada proceso). Si el valor ‘PETSC_DECIDE’ no se utiliza para las dimensiones locales, el programador ha de

92

Page 30: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

tener en cuenta la discusión anterior sobre la compatibilidad de las dimensiones locales con las dimensiones de la matriz ‘A’ y de los vectores ‘x’ y ‘b’.

Un tema importante a tratar, es el asunto del prealojamiento de espacio en memoria para la matriz paralela. El caso ahora es distinto al de matrices secuenciales.

Primeramente, empecemos definiendo el concepto de submatriz diagonal de un proceso. Sabemos que la matriz se distribuye por grupos de filas a través de los procesos que intervendrán en la ejecución de la aplicación paralela. El número de filas que pertenecen a un proceso, es la dimensión principal de la submatriz local; de modo que la matriz global es la unión de las submatrices almacenadas en cada proceso (todas las submatrices tendrán, lógicamente, el mismo número de columnas, pues la matriz se distribuye por filas completas). En este supuesto, la submatriz diagonal de un proceso es la correspondiente a la parte de la submatriz total, almacenada en dicho proceso, que es cuadrada (misma dimensión principal y secundaria) y, además, dicho bloque se encuentra en la diagonal de la matriz global. Para aclarar todo esto se presenta la figura 31.

Figura 31. Distribución de una matriz paralela a lo largo de los procesos en grupos de filas. Concepto de submatriz diagonal y offset en cada proceso.

La submatriz offset es la parte de la submatriz local, perteneciente a un proceso, que no pertenece a la submatriz diagonal. Si existen varios bloques offset, la submatriz offset total es la unión de estos bloques. La figura 31 es, de nuevo, bastante aclaratoria.

Una vez definido lo que es la submatriz diagonal y offset de un proceso, podemos abordar el asunto del prealojamiento de memoria para matrices AIJ paralelas. La forma de realizar el prealojamiento es indicando a la rutina ‘MatCreateMPIAIJ’ el número de elementos distintos de cero de la submatriz diagonal y de la submatriz offset, por separado. Al igual que en el caso de matrices AIJ secuenciales, ahora también se le puede indicar a la rutina en cuestión, el número de no ceros para todas las filas (mediante un escalar) o el número de no ceros para cada fila (mediante un array). La única diferencia es que aquí habrá dos escalares o dos arrays, uno para la submatriz diagonal y otro para la submatriz offset, ambas locales al proceso. Si se especifica el número de no ceros de alguna submatriz mediante un array, se

93

Page 31: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

ignora el correspondiente escalar destinado al mismo fin. Por ejemplo, si indicamos el número de no ceros de la submatriz diagonal local al proceso mediante un array, el argumento de tipo escalar destinado a proporcionar semejante información se ignora.

Al igual que en el caso secuencial, cuanto más precisos seamos a la hora de determinar el número de elementos distintos de cero de las submatrices, más eficiente será el funcionamiento de nuestra aplicación, puesto que la PETSc tendrá que realizar menor alojamiento de memoria dinámico (en tiempo de ejecución). Por esta razón, de nuevo, es aconsejable realizar un pequeño código anterior al prealojamiento de memoria para computar los no ceros de la matriz de nuestra aplicación, matriz que puede estar almacenada en un fichero. Si, por el contrario, dicha matriz fuera generada por nuestro código, por ejemplo de forma aleatoria, sabríamos, aproximadamente, el número de no ceros si sabemos el número de veces que insertaremos elementos en nuestra matriz, siempre y cuando seamos conscientes de la estructura que deseamos.

Es importante hacer notar que la separación de las submatrices locales diagonal y offset de un proceso se realiza solamente para la fase de prealojamiento de memoria. Una vez terminada esta etapa, ya no es necesario hacer referencia a ambas submatrices.

Una vez concluida la fase de creación y prealojamiento de memoria de la matriz paralela, le sigue el paso de inserción de valores. A diferencia del caso secuencial, ahora la matriz se encuentra distribuida por submatrices a lo largo de los procesos que ejecutan la aplicación paralela. De este modo, cualquier proceso puede insertar valores en cualquier posición de la matriz global, sólo que es más eficiente que cada uno lo haga exclusivamente, si es posible, para los elementos de la matriz que son locales al proceso. Para lograr esto, cada proceso ha de saber el rango de filas de la matriz global que le pertenece, que se obtiene mediante la rutina ‘MatGetOwnershipRange’. De esta manera, en la posterior fase de ensamblado (llamadas a las rutinas ‘MatAssemblyBegin’ y ‘MatAssemblyEnd’), el número de valores no locales a colocar dentro de la matriz global será mínimo, lo que disminuye las comunicaciones, y que se traduce en un mayor rendimiento de la aplicación paralela.

Decir también, que tanto en el caso de matrices secuenciales como paralelas, si en el momento de ejecutar el programa, indicamos por línea de comandos la opción “-info”, al realizar el ensamblado se imprimirá por pantalla la información correspondiente al prealojamiento de memoria para la matriz en cuestión.

3.5.2.2. MATRICES DENSAS.

Es poco lo que hay que decir sobre matrices densas. La librería PETSc suministra dos rutinas, ‘MatCreateSeqDense’ y ‘MatCreateMPIDense’, las cuales crean, respectivamente, una matriz densa y otra paralela. Ambas rutinas permiten a cada proceso llamante almacenar las matrices en un array local por columnas, como si estuviéramos utilizando arrays de dos dimensiones de Fortran. En adición, PETSc permite al usuario de Fortran, pasar a las rutinas un array donde se ubicará la matriz. Si dicho argumento presenta el valor “PETSC_NULL”,será PETSc la encargada de alojar el correspondiente espacio para la matriz.

Apuntar que PETSc proporciona más rutinas para la gestión de matrices dispersas que para matrices densas. Tal es así, que PETSc no proporciona solvers directos para matrices densas; más bien, el objetivo de PETSc se centra en los solvers iterativos para matrices dispersas.

94

Page 32: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.5.3. OPERACIONES CON MATRICES.

De la misma manera que se hizo en el apartado de vectores, presentaremos a continuación una tabla (tabla 7) con las principales operaciones que se pueden realizar con matrices.

RUTINA OPERACIÓN REALIZADAMatAXPY Y = Y + a · X MatMult y = A · x MatMultAdd z = y + A · x MatMultTranspose y = AT · x MatMultTransposeAdd z = y + AT · x MatNorm r = ||A||type

MatDiagonalScale A = diag(l) · A · diag(r)MatScale A = a · A MatConvert B = A MatCopy B = A MatGetDiagonal x = diag(A) MatTranspose B = AT

MatZeroEntries A = 0 MatShift Y = Y + a · I MatMatMult C = A · B

Tabla 7. Operaciones básicas con matrices.

Las interfaces para estas rutinas se encuentran en el manual HTML de PETSc [26].

Comentaremos algunas de las rutinas que se presentan en la tabla y que pueden llevar a confusión.

La rutina ‘MatZeroEntries’, pone a cero los elementos de la matriz en cuestión, pero mantiene la estructura de los datos de la matriz original, en particular se conservan los datos que indican las posiciones de los elementos distintos de cero. Es interesante la utilización de la ya comentada rutina ‘MatSetOption’ con el valor “MAT_NO_NEW_NONZERO_LOCATIONS”, que previene el alojamiento adicional de memoria, y descarta aquellos valores que se intentan insertar en posiciones no reservadas antes del ensamblado de la matriz original.

Para el caso de vectores, disponíamos de la rutina ‘VecGetArray’ para acceder de forma directa a los elementos de un vector, y la rutina ‘VecRestoreArray’ era la encargada de devolver el array a la memoria. Ahora, para el caso de matrices, por medio de ‘MatGetRow’se puede acceder directamente a las componentes de una matriz, aunque solamente es posible acceder a las filas locales al proceso llamante. Por medio de esta rutina, sólo se puede visualizar los valores de las componentes de la matriz, no modificarlos. Para lograrlo, sería necesario llamar a la rutina ‘MatSetValues’. Una vez examinado los valores, es conveniente llamar a ‘MatRestoreRow’, de modo que se libere el espacio adicional utilizado tras la llamada a ‘MaGetRow’.

Al igual que ocurría con vectores, en el caso de matrices, la rutina ‘MatView’ muestra el contenido de un objeto de tipo matriz.

95

Page 33: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.6. KSP: SOLVERS DE ECUACIONES LINEALES.

3.6.1. INTRODUCCIÓN.

El nombre de KSP, viene del conjunto: método del subespacio de Krylov (KS) más un precondicionador (P).

El objeto KSP es la pieza principal de todo el entramado de objetos que proporciona PETSc. Este objeto, ofrece acceso uniforme y eficiente a todos los paquetes de solvers de sistemas lineales. Los tipos de solvers lineales pueden ser paralelos o secuenciales y directos o iterativos, utilizando las mismas secuencias de llamadas a la hora de invocarlos.

Los precondicionadores son otro tipo de objeto que suministra PETSc (objetos PC), y su uso es concluyente para resolver un sistema.

Ambos tipos de objetos, se proporcionan como módulos independientes dentro del árbol de directorios de la librería PETSc, incluso pueden utilizarse individualmente. Sin embargo, es más útil emplear las interfaces proporcionadas por los objetos KSP para hacer uso de los precondicionadores, ya que, normalmente, los precondicionadores se utilizan en conjunto con los solvers de sistemas.

3.6.2. OBJETOS KSP.

Antes de resolver un sistema lineal mediante un objeto KSP, es necesario crear el contexto del solver lineal mediante la rutina ‘KSPCreate’, la cual devuelve el objeto KSP encargado de resolver el sistema de nuestra aplicación particular.

Tras crear el contexto del solver, es necesario especificar al objeto KSP la matriz del sistema a resolver, lo cual se logra mediante la rutina ‘KSPSetOperators’. Mediante esta rutina, también es posible especificar la matriz que actuará como precondicionador.

Con un mismo contexto KSP, es posible resolver varios sistemas diferentes, pero estos han de tener las mismas dimensiones y ser resueltos con el mismo método de precondicionamiento. Sin embargo, hay que tener en cuenta que las matrices que representan estos sistemas pueden tener una estructura diferente de elementos distintos de cero. Esto se representa mediante un parámetro que se le pasa como argumento a la rutina ‘KSPSetOperators’.

Existen diversos métodos para resolver sistemas lineales. De todos ellos, PETSc proporciona soporte para los mostrados en la tabla 8. Mediante la rutina ‘KSPSetType’, se puede especificar la técnica utilizada para resolver un sistema concreto. Si no se utiliza esta rutina, el método utilizado por defecto es el de GMRES (Residuo Mínimo Generalizado), que ha sido el empleado en la fase de pruebas del proyecto.

Respecto a esto, decir que la rutina ‘KSPGMRESSetRestart’ se encarga de ajustar el parámetro de reinicio del algoritmo GMRES. Para que surta efecto esta rutina, es necesario llamarla tras ‘KSPSetFromOptions’, que se verá con posterioridad.

En la introducción a esta sección se comentó que, aunque se podía acceder a los objetos precondicionadores a través de las interfaces de los objetos KSP, también era posible utilizar rutinas que trabajasen directamente con estos objetos. Por ejemplo, tras obtener el objeto precondicionador utilizado con un objeto KSP mediante ‘KSPGetPC’, es posible indicar el tipo de precondicionador a utilizar mediante la rutina ‘PCSetType’. En la tabla 9, se indican

96

Page 34: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

los precondicionadores soportados por PETSc. Se aconseja consultar el manual HTML de PETSc antes de utilizar algún precondicionador concreto. También, en el apartado 4.4 del manual de usuario de PETSc se habla bastante del tema de los precondicionadores.

Método KSP Alias Richardson KSPRICHARDSON Chebychev KSPCHEBYSHEV

Gradiente Conjugado KSPCG Gradiente BiConjugado KSPBICG

Residuo Mínimo Generalizado (GMRES) KSPGMRES BiCGSTAB KSPBCGS

Gradiente Conjugado al cuadrado KSPCGS Residuo Cuasi-mínimo Libre-Transpuesto (1) KSPTFQMR Residuo Cuasi-mínimo Libre-Transpuesto (2) KSPTCQMR

Residuo Conjugado KSPCRMétodo de los mínimos cuadrados KSPLSQR

Shell para métodos no KSP. KSPPREONLY

Tabla 8. Métodos de resolución de sistemas lineales.

Precondicionador AliasJacobi PCJACOBI

Jacobi en bloque PCBJACOBI SOR (y SSOR) PCSOR

Cholesky incompleto PCICCLU incompleto PCILUSchwarz aditivo PCASM

Solver lineal PCKSP

Combinación de precondicionadores PSCOMPOSITE

LU PCLUCholesky PCCholesky

Sin precondicionador PCNONE Shell para precondicionador de usuario PCSHELL

Tabla 9. Precondicionadores soportados por PETSc.

Además de definir el contexto KSP y el precondicionador a utilizar, es posible indicar diversas características del objeto KSP. Concretamente, por medio de la rutina ‘KSPSetTolerances’, muy utilizada en las pruebas, es posible especificar las tolerancias requeridas al resolver el sistema.

Todas las opciones vistas hasta ahora, tales como el tipo de método de resolución a utilizar, el precondicionador o las tolerancias, se pueden indicar a través de línea de comandos, mediante las opciones de la base de datos que mantiene la aplicación. La rutina ‘KSPSetFromOptions’ es la encargada de recoger estas opciones y aplicarlas durante la

97

Page 35: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

ejecución del programa. Si se invoca a esta rutina, se sobrescribirán los argumentos especificados por las rutinas anteriores que hagan referencia a los mismos parámetros que los indicados por línea de comandos. Por ejemplo, es posible indicar el tipo de objeto KSP y precondicionador mediante las opciones de línea de comandos ‘-ksp_type’ y ‘pc_type’, respectivamente, en vez de emplear las rutinas destinadas a tal fin.

Existen muchas otras características de los objetos KSP y PC que se pueden ajustar mediante las rutinas proporcionadas por la PETSc, tales como el tipo de ortogonalización del GMRES o como el lado de precondicionado (izquierda/derecha). En el manual HTML de PETSc se reúne un conjunto variado de rutinas, útiles para aplicaciones particulares, por esta razón se aconseja encarecidamente acudir al manual antes de implementar cualquier aplicación, ya que no todas las rutinas son aptas para todo tipo de aplicaciones, sino que cada cual tiene su función específica.

Una vez creado el contexto KSP y ajustados todos los parámetros, se puede pasar a resolver el sistema con la rutina ‘KSPSolve’ (no hace falta decir la gran importancia de esta rutina, es la encargada de hallar el vector solución). Un poco menos importante es la rutina ‘KSPView’, que presenta un resumen de las características del solver del objeto KSP.

Tras la resolución del sistema, puede ser interesante conocer el número de iteraciones realizadas por el solver iterativo, cosa que se puede conseguir a través de ‘KSPGetIterationNumber’.

Por último, decir que al término del programa, es importante liberar el objeto KSP creado (como se hacer con cualquier otro tipo de objeto) mediante la llamada a ‘KSPDestroy’.

Uno de los temas más importantes es el asunto de la convergencia del solver utilizado para resolver un problema. A través de ciertas opciones, PETSc ofrece información sobre las iteraciones que va realizando el solver.

Existen tres formas de monitorización del solver:

1. Con las opciones de línea de comandos ‘-ksp_monitor’ y ‘-ksp_xmonitor’, es posible imprimir y representar, respectivamente, la norma residual precondicionada.

2. De la misma forma, mediante ‘-ksp_truemonitor’ y ‘-ksp_xtruemonitor’, es posible visualizar la norma residual verdadera (la correspondiente a || b - Ax ||).

3. El usuario también tiene la opción de crear sus propias rutinas de monitorización (si es necesario, se aconseja indagar en el manual HTML de PETSc comenzando por la rutina ‘KSPSetMonitor’).

Con esto último, concluye la parte de solvers lineales a través de objetos KSP, los objetos más importantes y cuidados de PETSc. La siguiente sección, es un breve resumen de algunos de los paquetes restantes de la librería paralela que venimos estudiando.

98

Page 36: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

3.7. OTROS MÓDULOS DE PETSC.

3.7.1. INTRODUCCIÓN.

En esta parte, haremos una pequeña introducción al soporte ofrecido por PETSc de solvers no lineales, correspondiente al paquete SNES, y solvers de ecuaciones diferenciales ordinarias escalables, del paquete TS.

El paquete TS de PETSc, proporciona un esqueleto para la solución escalable de ecuaciones diferenciales ordinarias (provenientes de discretizaciones de ecuaciones en derivadas parciales dependientes del tiempo) y de problemas en régimen permanente.

El capítulo 6 del manual de usuario de PETSc presenta una descripción detalla de los objetos TS.

A continuación, realizaremos un pequeño acercamiento al paquete SNES de PETSc.

3.7.2. SOLVERS NO LINEALES.

El paquete SNES contiene un gran conjunto de rutinas numéricas con estructura de datos neutral, capaz de resolver problemas no lineales de grandes dimensiones.

La gran ventaja de SNES, es que da la posibilidad al usuario de personalizar los solvers no lineales en función de la aplicación a tratar.

Para problemas provenientes de ecuaciones en derivadas parciales (PDE’s), SNES proporciona la solución general para F(u) = 0, donde F es una función tal que F: Rn Rn. El usuario se encarga de proporcionar el código para evaluar F(u) y, opcionalmente, el código para evaluar el Jacobiano de F(u). También es posible utilizar aproximación por diferencia finita dispersa o diferenciación automática.

El núcleo del paquete SNES lo constituyen las técnicas basadas en el método de Newton, incluyendo:

- Procedimientos de búsqueda de línea. - Aproximaciones por región de verdad. - Continuación pseudo-transitoria. - Métodos de matrices libres.

SNES permite personalizar todas las fases del proceso de solución, esto es, el usuario puede cambiar cualquier aspecto del proceso de solución en tiempo de ejecución.

Las rutinas destacables de este paquete se presentan a continuación:

SNESCreate: crea el contexto para el solver no lineal.SNESSetFunction: por medio de esta rutina, el usuario especifica F(u).SNESSetJacobian: igual a la anterior, pero para el Jacobiano de F(u).SNESSetFromOptions: recoge las opciones de la base de datos.SNESSolve: resuelve el sistema no lineal.SNESView: visualización de las opciones en tiempo de ejecución del solver.SNESDestroy: destrucción del objeto SNES creado con anterioridad.

99

Page 37: 3 CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO …bibing.us.es/proyectos/abreproy/11374/fichero/MEMORIA... · 3.1. INTRODUCCIÓN. Hemos llegado a la parte

Habría que decir muchas más cosas sobre los objetos SNES; sin embargo, en la fase de pruebas del proyecto, nos hemos limitado a resolver sistemas lineales, por lo que no hemos utilizado este tipo de objetos.

100