8 8.. ANEXO II: CÓDIGO FUENTE DE LAS...

59
8 8 8 . . . ANEXO II: CÓDIGO FUENTE DE LAS PRUEBAS. 134

Transcript of 8 8.. ANEXO II: CÓDIGO FUENTE DE LAS...

888... AANNEEXXOO IIII:: CCÓÓDDIIGGOO FFUUEENNTTEE DDEE LLAASSPPRRUUEEBBAASS..

134

8.1. INTRODUCCIÓN.

En esta sección, se presentan los códigos fuente de las aplicaciones paralelas diseñadas en la fase de pruebas del proyecto. Así mismo, se incluyen algunos ficheros de apoyo utilizados en la compilación y ejecución de las aplicaciones, tales como makefiles, scripts, etc.

Los ficheros se intentarán presentar por orden cronológico de realización a lo largo del proyecto. Comentar que se realizó una prueba para lenguaje C, y diversas pruebas para Fortran, ya que éste último es el que se utilizará como lenguaje predeterminado en futuras implementaciones de aplicaciones.

Todos los códigos están bastante comentados, buscando el ser auto explicativos. No obstante, es posible que se realice alguna aclaración al inicio o final de los mismos.

El estudio de estos códigos en profundidad es crucial para entender el funcionamiento de todo lo explicado sobre la librería PETSc, y diversas características de las rutinas empleadas en el código que no serían apreciables si no se observaran en un contexto de aplicación concreto.

La forma de trabajar al escribir nuevos códigos PETSc, es utilizar como esqueleto los códigos aquí presentados, o los códigos de ejemplo que proporciona PETSc integrados en el paquete de instalación. Al menos, durante un principio más o menos largo, ésta es la forma más sencilla y eficaz de trabajar, ya que evita errores debidos a despistes y se reutiliza código, que siempre es aconsejable, pues será más eficiente si ya se probó en solitario y se ajustó a los requerimientos del sistema en que se desarrollan las aplicaciones. Además, así es como se hizo en la fase de pruebas del proyecto.

Sin más dilación, pasemos a ver los códigos. Estos también se encuentran en el directorio “unidadCD:/Pruebas/pruebas-petsc/pruebas_C”, para los códigos escritos en lenguaje C, y en “unidadCD:/Pruebas/pruebas-petsc/pruebas_fortran”, para los códigos escritos en Fortran. Decir también, que en el directorio “unidadCD:/Software/SCRIPT PARA EJECUCION PARALELO (copiar en home)” se encuentra el script “paralelo.sh”, para ejecución en paralelo de programas (ya comentado en la sección 7), así como los ficheros de apoyo al script “nodos.awk” y “extrae_p.awk”, escritos en lenguaje AWK. Estos tres ficheros también se presentan aquí.

135

8.2. HERRAMIENTAS PARA LA EJECUCIÓN EN PARALELO.

La ejecución en paralelo se realiza mediante scripts escritos en Python, y los suministra MPI. Indicar que es necesario copiar, antes de la ejecución, los ejecutables de los programas en todas las máquinas que intervendrán en el funcionamiento paralelo. También es necesario especificar algunas variables de entorno, fundamentales para el correcto funcionamiento de las ejecuciones.

Para agrupar toda esta problemática, se optó por escribir un shell script, que realizara todo lo comentado. Este script es paralelo.sh, cuyo código se muestra a continuación. Seguidamente, se presentan dos ficheros escritos en AWK, y que se utilizan como apoyo para el script mencionado.

#!/bin/bash################################################################################ # # Fichero: 'paralelo.sh' # # # # Descripcion: Script para la copia de un programa en todos los ordenadores # # del cluster, y posterior ejecución de dicho programa en # # paralelo en un numero de nodos indicado por linea de # # comandos. # # # # # # Autor: Ignacio Campos Rivera # # Fecha: 26/10/2006 # # Actualizacion: 16/11/2006 # # # # DEPENDENCIAS: 'nodos.awk' y 'extrae_p.awk' # # # ##############################################################################

# IMPORTANE: ## Suponemos que el usuario 'josea' existe y tiene una misma cuenta con un# mismo password en cada nodo del cluster. ## Si esto no fuese así, hemos de crearlo o cambiar el nombre de la variable # 'usu' #usu=josea### Del mismo modo, la carpeta compartida por defecto en cada maquina es # '//maq_n/temp', y dentro de ella se encuentra /cygwin/home. Si queremos# cambiar esta configuracion, hemos de cambiar la variable 'comp'. # OJO: no poner '/' ni al principio ni al final en esta variable. #comp=temp/cygwin/home##

# Comprobamos el numero de argumentos pasados al script # $1 >> numero de nodos # $2 >> nombre programa a ejecutar en paralelo # $i, i=3,.. >> argumentos del programa

NODOS=$1PROG=$2

################################################################# Funcion para copiar un programa en todos los nodos, ##

136

## en //maq_num/$comp ## ## ## ## Utilizamos 'awk' para conseguir nombre de cada maquina ## ## Dichas maquinas estan registradas en: \ ## ## '/cygdrive/c/$comp/$usu/mpd.hosts' ## # #function copiap () {

local i=0

while [ $i -lt $NODOS ]; do

maq=`cat /home/$usu/mpd.hosts | awk -f nodos.awk NUM_MAQ=$i`

#Carpeta compartida en //maquina_n/comp cp $1 //$maq/$comp

#Si hubiera que copiar mas ficheros (de apoyo al programa, etc.) #este es el sitio para añadir las sentencias necesarias. #Por ejemplo: cp /home/josea/dim.txt //maq/comp

#Esto lo hacemos asi para no complicar mas el script, aunque se #podria realizar teniendo en cuenta el numero de argumentos #pasados al script ...

local i=`expr $i + 1`

done}## ## ###############################################################

################################################################# Funcion para eliminar el programa de todos los nodos ## ## tras acabar la ejecucion. ## # # function rmp () {

local i=0

while [ $i -lt $NODOS ]; do

maq=`cat /home/$usu/mpd.hosts | awk -f nodos.awk NUM_MAQ=$i`

#Carpeta compartida en //maquina_n/comp rm -f //$maq/$comp/$1

local i=`expr $i + 1`

done}

## ## ###############################################################

# Comprobamos el numero de argumentos pasados, al menos han de existir los #argumentos 'numero_nodos' y 'nombre_prog.exe' => $# >= 2

if [ $# -lt 2 ]; then echo "*****************************************************************" echo "* *" echo "* Uso: bash paralelo.sh num_nodos nombre_prog.exe args_programa *"

137

echo "* *" echo "* O bien: *" echo "* *" echo "* Primero dar permiso de ejecucion: chmod +x paralelo.sh *" echo "* Uso: ./paralelo.sh num_nodos nombre_prog.exe args_programa *" echo "* *" echo "* << Dar ruta absoluta (syntaxis Cygwin) ubicación programa >> *" echo "* *" echo "*****************************************************************" exitfi

################################################################ Copiar programa a todos los nodos implicados ###############################################################

copiap $PROG

################################################################ Ejecucion del programa con MPICH2 mediante el script 'mpiexec' ################################################################# Primero arrancamos un demonio por nodo con 'mpdboot' #mpdboot -n $NODOS -f /home/$usu/mpd.hosts ##

# Coleccionamos argumentos del programa en una variable 'args' num_args=`expr $# - 3` fich_temp=temp.txt

j=0

echo -n "" > $fich_temp

while [ $j -le $num_args ]; do

echo -n "$3 " >> $fich_temp shift j=`expr $j + 1`

done

#args=`cat $fich_temp | awk '{printf("%s", $0)'` args=`cat $fich_temp`

# Eliminamos el fichero temporal rm -f $fich_temp

################################################################### Ejecución en paralelo #### ## ##

#MAQ_LOCAL=`/usr/bin/hostname`PROG=`echo -n $PROG | awk -f extrae_p.awk`

# La opcion '-log_summary' provoca que el programa presente # un resumen de lo realizado al final de la ejecucion.

mpiexec -n $NODOS /cygdrive/c/$comp/$PROG $args -log_summary

# # ###############################################################

138

################################################################ Cerramos los demonios en todas las maquinas # ###############################################################

# echo "" # echo "IMP: Cerrar anillo demonios 'mpd' con 'mpdallexit' desde nodo maestro" #

echo "" echo -n "Destruyendo anillo de demonios mpd con 'mpdallexit'... "

mpdallexit

echo "OK"

#Eliminamos el programa paralelo de todos los nodos rmp $PROG

############################################################################### Fin de 'paralelo.sh' ##############################################################################

139

A continuación, se presenta el fichero nodos.awk.

#!/bin/bash################################################################################# # # Fichero: 'nodos.awk' # # # # Descripcion: Devuelve el nombre de la maquina especificado en el fichero # # '/cygdrive/c/$comp/$usu/mpd.hosts', y que se encuentra en la # # linea numero 'NUM_MAQ'. # # # # # # Autor: Ignacio Campos Rivera # # Fecha: 26/06/2006 # # # ##############################################################################

BEGIN { i=0; }

{

# Buscamos la linea que corresponda al numero de maquina buscada if(i == NUM_MAQ){ printf("%s", $1); }i++;

}

################################################################################Fin de 'nodos.awk' ###############################################################################

140

Ahora, el fichero extrae_p.awk.

#!/bin/bash################################################################################# # # Fichero: 'extrae_p.awk' # # # # Descripcion: Devuelve el nombre del programa a ejecutar en paralelo, # # pasado como argumento al script 'paralelo.sh' pero con la # # ruta absoluta. # # # # Autor: Ignacio Campos Rivera # # Fecha: 26/06/2006 # # # ##############################################################################

BEGIN {

i = 0;

}{ s=$0;

i = split (s, a, /\//);

printf("%s", a[i]);

}

############################################################################### Fin de 'extrae_p.awk' # ##############################################################################

141

8.3. CÓDIGOS PARA LENGUAJE C.

Solamente se ha escrito un programa en C, debido a que las aplicaciones paralelas de interés se han realizado en Fortran. El código de la aplicación en cuestión se encuentra almacenado en el fichero ‘prueba1.c’, y le acompaña el correspondiente makefile para la compilación (y enlazado) del código fuente.

El fichero ‘prueba1.c’ se presenta a continuación.

/***************************************************************************** * * Descripcion: Ejemplo de uso de algunas de las rutinas mas importantes de * las librerias de matrices y vectores de PETSc. * * Autor: Ignacio Campos Rivera, 31/10/2006 * *****************************************************************************/

static char help[] = "\n**** Ejemplo de creacion de matrices y vectores y\n operaciones implicadas. ****\n\n";

#include <stdlib.h> // para la funcion rand() #include "petscmat.h"

#undef __FUNCT__ #define __FUNCT__ "main"

int main(int argc, char **args){ Vec x, b; // realizaremos la operacion Ax = b, Mat A; // con A y x conocidos, b sera el resultado.

/* especificamos el valor por defecto de 'm' (numero de filas locales de la matriz paralela), por si no se especifica como argumento del programa (opcion de base de datos). */

PetscInt m = 10, N, Istart, Iend, i, j; PetscInt nl, *gindices, rstart, rend; PetscScalar v; PetscMPIInt rank, size;

PetscErrorCode ierr;

// Automaticamente llama a MPI_Init() PetscInitialize(&argc, &args, (char *)0, help);

// Obtenemos numero procesadores (procesos si secuencial) de que disponemos. ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr);

// Obtenemos el numero de proceso local. ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr);

// Conseguimos el valor entero para una opcion particular en la base de datos // En este caso, 'm' es el numero de filas locales por proceso (todos // mismo numero de filas). ierr = PetscOptionsGetInt(PETSC_NULL, "-m", &m, PETSC_NULL); CHKERRQ(ierr);

142

// Numero de columnas (numero global). N = m * size;

/* Creamos una matriz dispersa distribuida, especificando la dimension local, es decir, el numero de filas que pertenencen localmente a un proceso. La dimension de la submatriz local sera m*N, donde N = m*size (matriz cuadrada). La dimension de la matriz global sera M*N, donde N = M = m*size.

>>> Ver el ejemplo 'ex2.c' de matrices, es interesante.

Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no realiza el prealojamiento de memoria, es por ello que es necesario llamar a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la siguiente: MatCreateMPIAIJ(MPI_Comm comm,PetscInt m,PetscInt n,PetscInt M, PetscInt N,PetscInt d_nz, const PetscInt d_nnz[], PetscInt o_nz,const PetscInt o_nnz[],Mat *A) */ ierr = MatCreateMPIAIJ(PETSC_COMM_WORLD ,m, PETSC_DECIDE, PETSC_DETERMINE,\ N, m, PETSC_NULL, N - m, PETSC_NULL, &A); CHKERRQ(ierr);

ierr = MatSetFromOptions(A); CHKERRQ(ierr);

/* Actualmente, todos los formatos de matrices paralelas PETSc son particionadas por trozos contiguos de filas a traves de los procesadores. Es por esta razon por lo que debemos determinar que filas de la matriz son locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() */ ierr = MatGetOwnershipRange(A,&Istart,&Iend); CHKERRQ(ierr);

/* Establecemos los valores para los elementos de la matriz con la funcion rand(), que genera numeros aleatorios. Cada procesador necesita insertar solamente los valores de los elementos locales que le pertenezcan, pero cualquier elemento no local introducido sera enviado al procesador apropiado durante el ensamblado de la matriz.

Siempre hemos de referirnos a las filas y las columnas de la matriz con ordenacion global a la hora de insertar elementos. */ for (i=Istart; i<Iend; i++) { for (j=0; j<N; j++) { v = rand() * 0.0000001 * (rank + 1); ierr = MatSetValues(A,1,&i,1,&j,&v,ADD_VALUES); CHKERRQ(ierr); } }

/* Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos rutinas mientras se realiza el paso de mensajes. */ ierr = MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY); CHKERRQ(ierr); ierr = MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY); CHKERRQ(ierr);

/* Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension global, el particionamiento paralelo es determinado en tiempo de ejecucion.

143

El especificar la dimension global viene por la razon de que el vector sea compatible con las dimensiones de la matriz a la hora de realizar el producto. Primero creamos un vector desde cero y, posteriormente, lo duplicamos. */ ierr = VecCreate(PETSC_COMM_WORLD, &x); CHKERRQ(ierr); ierr = VecSetSizes(x, PETSC_DECIDE, N); CHKERRQ(ierr); ierr = VecSetFromOptions(x); CHKERRQ(ierr);

ierr = VecDuplicate(x, &b); CHKERRQ(ierr);

ierr = VecSet(x, 1.0); CHKERRQ(ierr); //Se podria obtener la longitud local del vector con:

// ierr = VecGetLocalSize(x, size_x); CHKERRQ(ierr); ierr = VecAssemblyBegin(x);CHKERRQ(ierr); ierr = VecAssemblyEnd(x);CHKERRQ(ierr);

/* Ahora haremos que cada procesador introduzca valores en su parte local del vector paralelo. Para ello, es necesario determinar la ordenacion local a global para el vector. Cada procesador genera una lista de indices globales para cada indice local.

Si hubiesemos especificado el tamanyo local del vector en vez del global, necesitariamos obtener el tamanyo global de la forma: ierr = VecGetSize(x,&N);CHKERRQ(ierr); */

// Obtenemos el rango local del vector paralelo: ierr = VecGetOwnershipRange(x, &rstart, &rend); CHKERRQ(ierr);

// numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart; //PetscPrintf(PETSC_COMM_SELF,"\n--< nl %D s %D e %D >--\n", nl, rstart,

// rend);

ierr = PetscMalloc(nl*sizeof(PetscInt), &gindices); CHKERRQ(ierr); gindices[0] = rstart;//PetscPrintf(PETSC_COMM_SELF,"\nrank:%D, gindices[0]=%D\n",

// (PetscInt)rank,gindices[0]);

for (i=0; i < nl-1; i++) { gindices[i+1] = gindices[i] + 1;

// PetscPrintf(PETSC_COMM_SELF,"\nrank:%D, gindices[%D]=%D\n", // (PetscInt)rank, i+1,gindices[i+1]); }

// Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar // los limites if (gindices[0] == -1) gindices[0] = N - 1; if (gindices[nl-1] == N) gindices[nl-1] = 0;

/* Realizamos el mapeado de indices locales a globales, de modo que utilizamos los indices locales para introducir elementos y automaticamente se introducen en el vector paralelo con su correspondiente indice global mapeado. */ { ISLocalToGlobalMapping ltog;

//El comunicador a utilizar debe contener unicamente el proceso local ierr = ISLocalToGlobalMappingCreate(PETSC_COMM_SELF, nl, gindices, &ltog); CHKERRQ(ierr); ierr = VecSetLocalToGlobalMapping(x, ltog); CHKERRQ(ierr); ierr = ISLocalToGlobalMappingDestroy(ltog); CHKERRQ(ierr); } ierr = PetscFree(gindices); CHKERRQ(ierr);

144

// CUALQUIER VECTOR DUPLICADO DE 'x' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO

/* Determinamos ahora los elementos del vector paralelo.

- Cada procesador determina sus valores locales utilizando ordenacion local.

- Cada procesador puede contribuir para cualquier entrada del vector paralelo, independientemente de a que procesador pertenezca el elemento. Cualquier contribucion no local sera transferida al procesador apropiado durante el proceso de ensamblado.

- Para colocar elementos con la ordenacion global se utiliza VecSetValues()

- El modo ADD_VALUES indica que todas las contribuciones seran anyadidas juntas. */ for (i=0; i < nl; i++) { v = (PetscScalar)rank + 1; ierr = VecSetValuesLocal(x,1,&i,&v,INSERT_VALUES); CHKERRQ(ierr); }

/* Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos rutinas mientras se realiza el paso de mensajes. */ ierr = VecAssemblyBegin(x);CHKERRQ(ierr); ierr = VecAssemblyEnd(x);CHKERRQ(ierr);

/* Vemos la matriz y el vector. Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo a un fichero. */ if(!rank) printf("\n\t ***** Matriz paralela A: *****\n\n"); ierr = MatView(A, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr);

if(!rank) printf("\n\t ***** Vector paralelo x: *****\n\n"); ierr = VecView(x, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr); /*

*/

// A continuacion realizamos la operacion Ax = b: ierr = MatMult(A, x, b);CHKERRQ(ierr); CHKERRQ(ierr);

// Visualizamos el resultado if(!rank) printf("\n ***** Vector paralelo b, donde Ax=b: *****\n\n"); ierr = VecView(b, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr);

// Destruimos la matriz y los vectores paralelos ierr = MatDestroy(A); CHKERRQ(ierr); ierr = VecDestroy(x); CHKERRQ(ierr); ierr = VecDestroy(b); CHKERRQ(ierr);

ierr = PetscFinalize(); CHKERRQ(ierr); return 0; }

/***************************************************************************** * Fin de 'prueba1.c' *****************************************************************************/

145

A continuación, el makefile correspondiente.

################################################################################ # # Descripcion: makefile para ficheros de prueba de la PETSc # # # # Utilización: teclear en linea de comandos: make prueba1 # # # # Autor: Ignacio Campos Rivera # # # ###############################################################################

CFLAGS = FFLAGS = CPPFLAGS = FPPFLAGS = LOCDIR = /home/Ignacio/pruebas-petsc EXAMPLESC = prueba1.c EXAMPLESF = MANSEC = Mat

include ${PETSC_DIR}/bmake/common/base

prueba1: prueba1.o chkopts -${CLINKER} -o prueba1 prueba1.o ${PETSC_MAT_LIB} ${RM} prueba1.o

146

8.4. CÓDIGOS PARA LENGUAJE FORTRAN.

Se han escrito diversos códigos de aplicaciones mediante el lenguaje Fortran. Iremos presentando uno a uno, realizando previamente una pequeña introducción a cada código.

Primeramente, presentaremos el makefile encargado de compilar todas las pruebas realizadas en Fortran.

################################################################################ # # Descripcion: makefile para ficheros de prueba de la PETSc # # # # Utilización: teclear en linea de comandos: make “objetivo” , donde # # “objetivo” es, por ejemplo, struct o KSPstruct, dependiendo # # del codigo que queramos compilar. # # # # Autor: Ignacio Campos Rivera # # # ###############################################################################

CFLAGS =FFLAGS =CPPFLAGS = FPPFLAGS = LOCDIR = /home/Ignacio/pruebas-petsc/pruebas_fortran EXAMPLESC = ex1.c EXAMPLESF = prueba1.F90 struct.F90 KSPstruct.F90 KSPstruct_Luis.F90 \ diag_dom.F90 diag_domN.F90 MANSEC = KSP

include ${PETSC_DIR}/bmake/common/base

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

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

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

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

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

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

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

147

El siguiente código a mostrar se encuentra en prueba1.F90, que es el mismo que el contenido en el fichero ‘prueba1.c’ de la sección anterior, pero mapeado de C a Fortran.

!*****************************************************************************!*!* Descripcion: Ejemplo de uso de algunas de las rutinas mas importantes de !* las librerias de matrices y vectores de la PETSc, en Fortran !*!* Autor: Ignacio Campos Rivera, 10/11/2006 !* !*****************************************************************************

program main implicit none

#include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h"

Vec :: x, b ! realizaremos la operacion Ax = b, Mat :: A ! con A y x conocidos, b sera el resultado.

! especificamos el valor por defecto de 'm' (numero de filas locales de la ! matriz paralela), por si no se especifica como argumento del programa ! (opcion de base de datos).

PetscInt :: m = 10, N, Istart, Iend, i, j PetscInt :: nl, rstart, rend PetscInt,ALLOCATABLE :: gindices(:) PetscScalar :: v PetscMPIInt :: rank, size PetscTruth :: flg PetscErrorCode :: ierr

PetscScalar :: rand integer,parameter :: seed = 86456

ISLocalToGlobalMapping ltog

! Automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr)

! Obtenemos numero procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr)

! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr)

! Conseguimos el valor entero para una opcion particular en la base de datos ! En este caso, 'm' es el numero de filas locales por proceso (todos! mismo numero de filas). call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-m', m, flg, ierr)

! Numero de columnas (numero global). N = m * size

148

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso.! La dimension de la submatriz local sera m*N, donde N = m*size (matriz! cuadrada). La dimension de la matriz global sera M*N, donde ! N = M = m*size. !! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. !! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la! siguiente:! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, PETSC_DECIDE, & PETSC_DETERMINE, N, m, PETSC_NULL_INTEGER, & N - m, PETSC_NULL_INTEGER, A, ierr) call MatSetFromOptions(A, ierr) call MatSetOption(A, MAT_COLUMN_ORIENTED) !IMPORTANTE

!! Actualmente, todos los formatos de matrices paralelas PETSc son! particionadas por trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() ! call MatGetOwnershipRange(A, Istart, Iend, ierr)

! Establecemos los valores para los elementos de la matriz con la funcion! rand(), que genera numeros aleatorios. Cada procesador necesita insertar ! solamente los valores de los elementos locales que le pertenezcan, pero! cualquier elemento no local introducido sera enviado al procesador ! apropiado durante el ensamblado de la matriz.

! Siempre hemos de referirnos a las filas y las columnas de la matriz con ! ordenacion global a la hora de insertar elementos.

!! In order to get a different sequence each time, we initialize the ! seed of the random number function with the sum of the current ! hour, minute, and second. !! call itime(timeArray) ! v = rand(timeArray(1) + timeArray(2) + timeArray(3))

call srand(seed)

! IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', PETSc esta implementada con C! (indices empiezan por '0'). Es por esta razon por la! cual el indice 'j' empieza en '0'.

do i=Istart, Iend-1, 1 do j=0, N-1, 1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr)

end do

149

end do

!! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y! MatAssemblyEnd(). ! Se pueden realizar calculos en medio de las dos rutinas mientras se! realiza el paso de mensajes. ! call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr)

!! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(),! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension! global, el particionamiento paralelo es determinado en tiempo de! ejecucion. !! El especificar la dimension global viene por la razon de que el vector! sea compatible con las dimensiones de la matriz a la hora de realizar el! producto. Primero creamos un vector desde cero y, posteriormente, lo! duplicamos. ! call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, PETSC_DECIDE, N, ierr) call VecSetFromOptions(x, ierr)

call VecDuplicate(x, b, ierr)

call VecSet(x, 1.0, ierr)

! Se podria obtener la longitud local del vector con: ! VecGetLocalSize(x, size_x, ierr)

call VecAssemblyBegin(x, ierr) call VecAssemblyEnd(x, ierr)

!! Ahora haremos que cada procesador introduzca valores en su parte local ! del vector paralelo. Para ello, es necesario determinar la ordenacion ! local a global para el vector. Cada procesador genera una lista de indices ! globales para cada indice local.!! Si hubiesemos especificado el tamanyo local del vector en vez del global, ! necesitariamos obtener el tamanyo global de la forma: ! ierr = VecGetSize(x,&N);CHKERRQ(ierr); !

! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(x, rstart, rend, ierr)

! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart ! PetscPrintf(PETSC_COMM_SELF,"\n--< nl %D s %D e %D >--\n", nl, rstart, rend);

ALLOCATE ( gindices(nl) )

gindices(1) = rstart

! PetscPrintf(PETSC_COMM_SELF,"\nrank:%D, gindices[0]=%D\n",! (PetscInt)rank,gindices[0]);

do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do

150

! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1).eq.-1) gindices(1) = N if ( gindices(nl).eq. N+1) gindices(nl) = 1

! Realizamos el mapeado de indices locales a globales, de modo que! utilizamos los indices locales para introducir elementos y! automaticamente se introducen en el vector paralelo con su! correspondiente indice global mapeado. !

! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(x, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr)

DEALLOCATE(gindices)

! CUALQUIER VECTOR DUPLICADO DE 'x' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO

!! Determinamos ahora los elementos del vector paralelo. !! - Cada procesador determina sus valores locales utilizando ordenacion! local. !! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. !! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() !! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas. !

! OJO: VecSetValuesLocal() utiliza indices basados en 0 para Fortran, al igual que ! en C

do i=0, nl-1, 1 v = rank + 1.0 call VecSetValuesLocal(x,1,i,v,INSERT_VALUES, ierr) end do

!! Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() ! y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos! rutinas mientras se realiza el paso de mensajes. ! call VecAssemblyBegin(x, ierr) call VecAssemblyEnd(x, ierr)

!! Vemos la matriz y el vector. Omitir los siguientes pasos si son! dimensiones grandes, o imprimirlo a un fichero. ! if(rank.eq.0) write(6,*) ' ***** Matriz paralela A: *****'

call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr)

if(rank.eq.0) write(6,*) ' ***** Vector paralelo x: *****'

call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr)

151

! A continuacion realizamos la operacion Ax = b: call MatMult(A, x, b, ierr)

! Visualizamos el resultado if(rank.eq.0) write(6,*) ' ***** Vector paralelo b (Ax=b): *****'

call VecView(b, PETSC_VIEWER_STDOUT_WORLD, ierr)

! Destruimos la matriz y los vectores paralelos call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(b, ierr)

call PetscFinalize(ierr)

end

!*****************************************************************************!* Fin de 'prueba1.F90' !*****************************************************************************/

152

A continuación, se presenta el código fuente de la aplicación ‘struct.F90’.

!*****************************************************************************!*!* Descripcion: Matriz sistema de estructuras !*!* Autor: ICR, 13/11/2006 !* !* Numero procesadores: 3, solo 3, por ahora. !*****************************************************************************

program main implicit none

! El pre-procesador de C se encargara de incluir las funciones necesarias ! declaradas en las siguientes localizaciones. A diferencia de C, en Fortran ! hay que incluir todos los ficheros necesarios, aun cuando unos incluyan a! otros. #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h"

! _____________________________________________________________________________ !! { Esquema del sistema } !!Num.Proceso! | _ nn1 nl1 nn2 nl2 nnf _ _ _ _ _ ! V | ------------------------------------ | | | | | ! /[ nn1 | K1 | C1 | 0 | 0 | 0 | | u1 | | f1 | ! P0 | ------------------------------------ | | ------- | | ----- | ! \[ nl1 | C1¬ | 0 | 0 | 0 | I1 | | lambda1 | | 0 | ! | ------------------------------------ | . | ------- | = | ----- | ! /[ nn2 | 0 | 0 | K2 | C2 | 0 | | u2 | | f2 | ! P1 | ------------------------------------ | | ------- | | ----- | ! \[ nl2 | 0 | 0 | C2¬ | 0 | I2 | | lambda2 | | 0 | ! | ------------------------------------ | | ------- | | ----- | ! P2 -[ nnf | 0 | I1¬ | 0 | I2¬ | 0 | | v | | 0 | ! | ------------------------------------ | | | | | ! - - - - - - ! A (M x N) x b !!! Notacion: '¬' = transpuesta ! -------- n1 = nn1 + nl1 ! n2 = nn2 + nl2 ! M = N = n1 + n2 + nnf ! _____________________________________________________________________________

Mat :: A Vec :: x, b PetscInt :: nn1, nn2, nl1, nl2, nnf PetscInt :: n1, n2, m, MG, NG PetscInt :: Istart,Iend, i, j, k, k2 PetscScalar :: v, uno = 1.0

PetscMPIInt :: rank, size PetscErrorCode :: ierr integer :: error=2, r integer,parameter :: seed = 836

153

! Para prealojar espacio en memoria para la matriz paralela:

PetscInt,ALLOCATABLE :: d_nz(:), o_nz(:)

! La siguiente variable es necesaria al utilizar PetscOptionsGetXxx(), a la ! hora de obtener valores con la opcion 'base de datos'. ! PetscTruth :: flg

! PetscInitialize() automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr)

! Obtenemos numero de procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr)

! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr)

! Comprobamos que haya exactamente 3 procesadores, si no, abortamos ejecucion if (size /= 3) then if(rank.eq.0) write(6,'(/,A,/)') & "Numero de procesadores necesarios: SOLO 3" stop 10001 end if

! Obtenemos las dimensiones de las submatrices contenidas en un fichero adjunto open(UNIT=10, FILE="dim.txt", STATUS="OLD", ACTION="READ", & IOSTAT=error)

if (error /= 0) then if(rank.eq.0) write(6,'(/,A,/)') & "Error de lectura de fichero 'dimension.txt'" stop 10002 end if

! La primera linea es un comentario, la ignoramos read (10,*) read (10,*) nn1, nn2, nl1, nl2, nnf

! Cerramos el fichero close(10)

! Dimensiones de las submatrices diagonales: n1 = nn1 + nl1 n2 = nn2 + nl2

! Dimensiones globales de la matriz paralela: MG = n1 + n2 + nnf NG = MG

! Especificamos el numero de filas de la matriz global que corresponderan ! a cada procesador. Tambien especificamos el numero de no-ceros de la! matriz diagonal y de la offset de cada procesador. Lo ideal es realizar ! un barrido sobre la matriz a tratar y asi obtener el numero exacto, ! aproximadamente, de no-ceros en cada fila de la matriz.

! En funcion del numero de proceso tendremos unas u otras dimensiones select case (rank)

! >>>> Proceso 0 <<<< case (0) m = n1 ALLOCATE (d_nz(m), o_nz(m))

do r=1, nn1 d_nz(r) = n1 ! no-ceros para K1-C1

o_nz(r) = 0 ! resto de elementos son 0

154

end do

do r=nn1+1, n1 d_nz(r) = nn1 ! no-ceros para C1¬ o_nz(r) = 1 ! Matriz identidad I1, solo '1s' en la diagonal end do

! >>>> Proceso 1 <<<< case (1) m = n2 ALLOCATE (d_nz(m), o_nz(m))

do r=1, nn2 d_nz(r) = n2 ! no-ceros para K2-C2 o_nz(r) = 0 ! resto de elementos son 0 end do

do r=nn2 +1, n2 d_nz(r) = nn2 ! no-ceros para C2¬ o_nz(r) = 1 ! Matriz identidad I2, solo '1s' en la diagonal end do

! >>>> Proceso 2 <<<< case (2) m = nnf ALLOCATE (d_nz(m), o_nz(m))

do r=1, nnf d_nz(r) = 0 ! no-ceros de la diagonal (son todos ceros) o_nz(r) = 1 + 1 ! el resto de elementos son los '1s' de I1¬ e I2¬ end do

end select

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso.!! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. !! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la! siguiente:! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) !

! IMPORTANTE: Como las submatrices diagonales son cuadradas, n=m ! PETSc da un fallo (dificil de detectar) si no especificamos el valor! del numero de columnas locales (n =m), ya que al crear el vector 'x'! especificaremos su dimension LOCAL. Si, por el contrario, especificamos la! dimension global de 'x', entonces podemos decirle a la PETSc que decida el! valor de n de la matriz en la forma n=PETSC_DECIDE.

! Sabemos que M = MG = SUMATORIO(m_proceso_local_i), pero le diremos a la PETSc ! que lo determine en tiempo de ejecucion. call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, m, PETSC_DETERMINE,& NG, 0, d_nz, 0, o_nz, A, ierr) call MatSetFromOptions(A, ierr)

! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED, ierr)

155

! Ya no se van a utilizar mas los arrays de indicacion de prealojamiento de! memoria, los destruimos. DEALLOCATE(d_nz, o_nz)

! Actualmente, todos los formatos de matrices paralelas PETSc son! particionadas en trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange()

call MatGetOwnershipRange(A, Istart, Iend, ierr)

! Establecemos los valores para los elementos de la matriz con la funcion! rand(), que genera numeros aleatorios.! Cada procesador necesita insertar solamente los valores de los elementos ! locales que le pertenezcan, pero cualquier elemento no local introducido ! sera enviado al procesador apropiado durante el ensamblado de la matriz. ! Importante poner el valor ADD_VALUES en vez de INSERT_VALUES si mas de un ! procesador interviene a la hora de colocar un valor en un elemento.

! IMPORTANTE: Siempre hemos de referirnos a las filas y las columnas de la! matriz con ordenacion global a la hora de insertar elementos.

call srand(seed)

! MUY IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C! (indices empiezan por '0'). Es por esta razon por la! cual el indice 'j' empieza en '0'.

! Nota: Istart - Iend = {n1 en P0, n2 en P1, nnf en P3}.! IMPORTANTE: Istart tambien empieza por '0' en el procesador maestro! (indexacion segun convenio de C).

! print *, "n1 = ", n1, "n2 = ", n2, "nnf = ", nnf, "MG = NG = ", NG ! print *, "rank = ", rank, "Iend-Istart = ", Iend - Istart ! print *, "rank = ", rank, "Istart = ", Istart, "Iend = ", Iend

select case (rank) ! >>>> Proceso 0 <<<<

case (0)

! Insertamos valores para: K1 y C1 do i=Istart, Istart+nn1-1 bK1: do j=0, nn1-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK1

bC1: do j=nn1, n1-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1

end do

! Insertamos valores para: C1¬ e I1 ! Si I1 es diagonal, el primer '1' de I1 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2

do i=Istart+nn1, Iend-1

bC1_: do j=0, nn1-1

156

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1_

v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

! >>>> Proceso 1 <<<< case (1)

! Insertamos valores para: K2 y C2 do i=Istart, Istart+nn2-1 bK2: do j=n1, n1+nn2-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK2

bC2: do j=n1+nn2, n1+n2-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2

end do

! Insertamos valores para: C2¬ e I2 ! Si I2 es diagonal, el primer '1' de I2 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2

do i=Istart+nn2, Iend-1 bC2_: do j=n1, n1+nn2-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2_

v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

! >>>> Proceso 2 <<<< case (2)

! Insertamos valores para: I1¬ e I2¬ k = nl1 k2 = nl1 + nl2

do i=Istart, Iend-1

v = 1.0 call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr)

call MatSetValues(A, 1, i, 1, k2, v, ADD_VALUES, ierr)

157

k = k + 1 k2 = k2 + 1 end do

end select

! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y! MatAssemblyEnd(). ! Se pueden realizar calculos, que no involucren el uso de la matriz A, en! medio de las dos rutinas mientras se realiza el paso de mensajes.

call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr)

! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(),! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension! global, el particionamiento paralelo es determinado en tiempo de! ejecucion. ! Primero creamos un vector desde cero y, posteriormente, lo! duplicamos.

call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, m, PETSC_DECIDE, ierr) call VecSetFromOptions(x, ierr)

call VecDuplicate(x, b, ierr)

call VecSet(x, uno, ierr)

! Se podria obtener la longitud local del vector con: ! VecGetLocalSize(x, size_x, ierr)

call VecAssemblyBegin(x, ierr) call VecAssemblyEnd(x, ierr)

! Visualizamos la matriz A y el vector x.! Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo ! a un fichero.

if(rank.eq.0) write(6,'(/,T20,A,/)') & ' ***** Matriz paralela A: *****'

call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr)

if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo x: *****'

call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr)

! A continuacion realizamos la operacion Ax = b: call MatMult(A, x, b, ierr)

! Visualizamos el resultado1 if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b (Ax=b): *****'

call VecView(b, PETSC_VIEWER_STDOUT_WORLD, ierr)

! Liberamos la memoria alojada para la matriz y los vectores call MatDestroy(A, ierr)

158

call VecDestroy(x, ierr) call VecDestroy(b, ierr)

call PetscFinalize(ierr)

end

!*****************************************************************************!* Fin de 'struct.F90' * !*****************************************************************************

159

El siguiente código corresponde al fichero ‘KSPstruct.F90’.

!*****************************************************************************!* Descripcion: Matriz sistema de estructuras * !* Primero haremos la multiplicacion A·x1 y obtendremos b. * !* Posteriormente resolveremos con KSP el sistema A·x2 = b * !* y comprobaremos que el valor de x1 y x2 es aproximadamente * !* el mismo en ambos casos (viendo la norma del vector x1-x2). * !* * !* Autor: ICR, 14/11/2006 * !* * !* Numero procesadores: 3, solo 3, por ahora. * !*****************************************************************************

program main implicit none

! El pre-procesador de C se encargara de incluir las funciones necesarias ! declaradas en las siguientes localizaciones. A diferencia de C, en Fortran ! hay que incluir todos los ficheros necesarios, aun cuando unos incluyan a! otros. #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h"

! _____________________________________________________________________________ !! { Esquema del sistema } !!Num.Proceso! | _ nn1 nl1 nn2 nl2 nnf _ _ _ _ _ ! V | ------------------------------------ | | | | | ! /[ nn1 | K1 | C1 | 0 | 0 | 0 | | u1 | | f1 | ! P0 | ------------------------------------ | | ------- | | ----- | ! \[ nl1 | C1¬ | 0 | 0 | 0 | I1 | | lambda1 | | 0 | ! | ------------------------------------ | . | ------- | = | ----- | ! /[ nn2 | 0 | 0 | K2 | C2 | 0 | | u2 | | f2 | ! P1 | ------------------------------------ | | ------- | | ----- | ! \[ nl2 | 0 | 0 | C2¬ | 0 | I2 | | lambda2 | | 0 | ! | ------------------------------------ | | ------- | | ----- | ! P2 -[ nnf | 0 | I1¬ | 0 | I2¬ | 0 | | v | | 0 | ! | ------------------------------------ | | | | | ! - - - - - - ! A (M x N) x b !!! Notacion: '¬' = transpuesta ! -------- n1 = nn1 + nl1 ! n2 = nn2 + nl2 ! M = N = n1 + n2 + nnf ! _____________________________________________________________________________

KSP :: ksp PC :: pc Mat :: A Vec :: x1, x2, b PetscInt :: nn1, nn2, nl1, nl2, nnf PetscInt :: n1, n2, m, MG, NG PetscInt :: Istart,Iend, i, j, k, k2, its

160

PetscScalar :: v, uno = 1.0, uno_neg = -1.0 PetscReal :: norma, tol = 1.d-7

PetscMPIInt :: rank, size PetscErrorCode :: ierr integer :: error=2, r integer,parameter :: seed = 836

! Para prealojar espacio en memoria para la matriz paralela: PetscInt,ALLOCATABLE :: d_nz(:), o_nz(:)

! La siguiente variable es necesaria al utilizar PetscOptionsGetXxx(), a la ! hora de obtener valores con la opcion 'base de datos'. ! PetscTruth :: flg

! PetscInitialize() automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr)

! Obtenemos numero de procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr)

! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr)

! Comprobamos que haya exactamente 3 procesadores, si no, abortamos ejecucion if (size /= 3) then if(rank.eq.0) write(6,'(/,A,/)') & "Numero de procesadores necesarios: SOLO 3" stop 10001 end if

! Obtenemos las dimensiones de las submatrices contenidas en un fichero adjunto open(UNIT=10, FILE="dim.txt", STATUS="OLD", ACTION="READ", & IOSTAT=error)

if (error /= 0) then if(rank.eq.0) write(6,'(/,A,/)') & "Error de lectura de fichero 'dimension.txt'" stop 10002 end if

! La primera linea es un comentario, la ignoramos read (10,*) read (10,*) nn1, nn2, nl1, nl2, nnf

! Cerramos el fichero close(10)

! Dimensiones de las submatrices diagonales: n1 = nn1 + nl1 n2 = nn2 + nl2

! Dimensiones globales de la matriz paralela: MG = n1 + n2 + nnf NG = MG

! Especificamos el numero de filas de la matriz global que corresponderan ! a cada procesador. Tambien especificamos el numero de no-ceros de la! matriz diagonal y de la offset de cada procesador. Lo ideal es realizar ! un barrido sobre la matriz a tratar y asi obtener el numero exacto, ! aproximadamente, de no-ceros en cada fila de la matriz.

! En funcion del numero de proceso tendremos unas u otras dimensiones select case (rank)

161

! >>>> Proceso 0 <<<< case (0) m = n1 ALLOCATE (d_nz(m), o_nz(m))

do r=1, nn1 d_nz(r) = n1 ! no-ceros para K1-C1 o_nz(r) = 0 ! resto de elementos son 0 end do

do r=nn1+1, n1 d_nz(r) = nn1 ! no-ceros para C1¬ o_nz(r) = 1 ! Matriz identidad I1, solo '1s' en la diagonal end do

! >>>> Proceso 1 <<<< case (1) m = n2 ALLOCATE (d_nz(m), o_nz(m))

do r=1, nn2 d_nz(r) = n2 ! no-ceros para K2-C2 o_nz(r) = 0 ! resto de elementos son 0 end do

do r=nn2 +1, n2 d_nz(r) = nn2 ! no-ceros para C2¬ o_nz(r) = 1 ! Matriz identidad I2, solo '1s' en la diagonal end do

! >>>> Proceso 2 <<<< case (2) m = nnf ALLOCATE (d_nz(m), o_nz(m))

do r=1, nnf d_nz(r) = 0 ! no-ceros de la diagonal (son todos ceros) o_nz(r) = 1 + 1 ! el resto de elementos son los '1s' de I1¬ e I2¬ end do

end select

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso.!! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. !! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la! siguiente:! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) !

! IMPORTANTE: Como las submatrices diagonales son cuadradas, n=m ! PETSc da un fallo (dificil de detectar) si no especificamos el valor! del numero de columnas locales (n =m), ya que al crear el vector 'x'! especificaremos su dimension LOCAL. Si, por el contrario, especificamos la! dimension global de 'x', entonces podemos decirle a la PETSc que decida el! valor de n de la matriz en la forma n=PETSC_DECIDE.

162

! Sabemos que M = MG = SUMATORIO(m_proceso_local_i), pero le diremos a la PETSc ! que lo determine en tiempo de ejecucion. call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, m, PETSC_DETERMINE,& NG, 0, d_nz, 0, o_nz, A, ierr) call MatSetFromOptions(A, ierr)

! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED, ierr)

! Ya no se van a utilizar mas los arrays de indicacion de prealojamiento de! memoria, los destruimos. DEALLOCATE(d_nz, o_nz)

! Actualmente, todos los formatos de matrices paralelas PETSc son! particionadas en trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange()

call MatGetOwnershipRange(A, Istart, Iend, ierr)

! Establecemos los valores para los elementos de la matriz con la funcion! rand(), que genera numeros aleatorios. ! Cada procesador necesita insertar solamente los valores de los elementos ! locales que le pertenezcan, pero cualquier elemento no local introducido ! sera enviado al procesador apropiado durante el ensamblado de la matriz. ! Importante poner el valor ADD_VALUES en vez de INSERT_VALUES si mas de un ! procesador interviene a la hora de colocar un valor en un elemento.

! IMPORTANTE: Siempre hemos de referirnos a las filas y las columnas de la! matriz con ordenacion global a la hora de insertar elementos.

call srand(seed)

! MUY IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C! (indices empiezan por '0'). Es por esta razon por la! cual el indice 'j' empieza en '0'.

! Nota: Istart - Iend = {n1 en P0, n2 en P1, nnf en P3}.! IMPORTANTE: Istart tambien empieza por '0' en el procesador maestro! (indexacion segun convenio de C).

! print *, "n1 = ", n1, "n2 = ", n2, "nnf = ", nnf, "MG = NG = ", NG ! print *, "rank = ", rank, "Iend-Istart = ", Iend - Istart ! print *, "rank = ", rank, "Istart = ", Istart, "Iend = ", Iend

select case (rank) ! >>>> Proceso 0 <<<<

case (0)

! Insertamos valores para: K1 y C1 do i=Istart, Istart+nn1-1 bK1: do j=0, nn1-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK1

bC1: do j=nn1, n1-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1

163

end do

! Insertamos valores para: C1¬ e I1 ! Si I1 es diagonal, el primer '1' de I1 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2

do i=Istart+nn1, Iend-1 bC1_: do j=0, nn1-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1_

v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

! >>>> Proceso 1 <<<< case (1)

! Insertamos valores para: K2 y C2 do i=Istart, Istart+nn2-1 bK2: do j=n1, n1+nn2-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK2

bC2: do j=n1+nn2, n1+n2-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2

end do

! Insertamos valores para: C2¬ e I2 ! Si I2 es diagonal, el primer '1' de I2 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2

do i=Istart+nn2, Iend-1 bC2_: do j=n1, n1+nn2-1

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2_

v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

164

! >>>> Proceso 2 <<<< case (2)

! Insertamos valores para: I1¬ e I2¬ k = nn1 k2 = n1 + nn2

do i=Istart, Iend-1

v = 1.0 call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) call MatSetValues(A, 1, i, 1, k2, v, ADD_VALUES, ierr) k = k + 1 k2 = k2 + 1 end do

end select

! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y! MatAssemblyEnd(). ! Se pueden realizar calculos, que no involucren el uso de la matriz A, en! medio de las dos rutinas mientras se realiza el paso de mensajes.

call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr)

! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(),! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension! global, el particionamiento paralelo es determinado en tiempo de! ejecucion. ! Primero creamos un vector desde cero y, posteriormente, lo! duplicamos. ! Aqui x = x1

call VecCreate(PETSC_COMM_WORLD, x1, ierr) call VecSetSizes(x1, m, PETSC_DECIDE, ierr) call VecSetFromOptions(x1, ierr)

call VecDuplicate(x1, b, ierr) call VecDuplicate(x1, x2, ierr) ! Para resolver con KSP posteriormente

call VecSet(x1, uno, ierr)

! Se podria obtener la longitud local del vector con: ! VecGetLocalSize(x, size_x, ierr)

call VecAssemblyBegin(x1, ierr) call VecAssemblyEnd(x1, ierr)

! Visualizamos la matriz A y el vector x.! Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo ! a un fichero.

if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****'

call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr)

if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo x1: *****'

call VecView(x1, PETSC_VIEWER_STDOUT_WORLD, ierr)

165

!______________________________________________________________________________

! A continuacion realizamos la operacion Ax = b: call MatMult(A, x1, b, ierr)

! Visualizamos el resultado1 if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b (Ax=b): *****'

call VecView(b, PETSC_VIEWER_STDOUT_WORLD, ierr)

if(rank.eq.0) write(6,'(3/)')

! ____________________________________________________________________________

! Resolvemos ahora el sistema lineal mediante KSP ! Ahora x = x2

! Creamos el contexto para el solver lineal

call KSPCreate(PETSC_COMM_WORLD, ksp, ierr)

! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve! tambien de matriz de precondicion.

call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr)

! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. !! - Las 3 siguientes sentencias son opcionales; todos estos parametros podrian ! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions().

call KSPGetPC(ksp, pc, ierr) call PCSetType(pc, PCJACOBI, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_INTEGER, ierr)

! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> !! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores.

call KSPSetFromOptions(ksp, ierr)

! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b, x2, ierr)

! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve().

call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr)

! Visualizamos el resultado: if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x2: *****' call VecView(x2, PETSC_VIEWER_STDOUT_WORLD, ierr)

166

! Chequeamos el error:

call VecAXPY(x2, uno_neg, x1, ierr) call VecNorm(x2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr)

! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if (norma .gt. 1.e-12) then write(6,100) norma, its else write(6,200) its endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if

! Todos los procesos han de esperar a que el maestro realice el bloque! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr)

! Liberamos la memoria alojada para la matriz, los vectores y el contexto KSP. call KSPDestroy(ksp); call MatDestroy(A, ierr) call VecDestroy(x1, ierr) call VecDestroy(x2, ierr) call VecDestroy(b, ierr)

call PetscFinalize(ierr)

end

!*****************************************************************************!* Fin de 'KSPstruct.F90' !*****************************************************************************

167

El código siguiente, se corresponde con el contenido en el fichero ‘KSPstruct_Luis.F90’.

!*****************************************************************************!* * !* Descripcion: Matriz sistema de estructuras * !* * !* Primero, resolveremos con KSP el sistema A·x = b1 * !* Posteriormente, realizaremos la multiplicacion A·x y * !* obtendremos b2. * !* Finalmente, comprobaremos que el valor de b1 y b2 es * !* aproximadamente el mismo en ambos casos (viendo la norma * !* del error). * !* * !* Autor: ICR, 14/11/2006 * !* * !* Numero procesadores: 3, solo 3, por ahora. * !*****************************************************************************

program main implicit none

! El pre-procesador de C se encargara de incluir las funciones necesarias ! declaradas en las siguientes localizaciones. A diferencia de C, en Fortran ! hay que incluir todos los ficheros necesarios, aun cuando unos incluyan a! otros. #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h"

! _____________________________________________________________________________ !! { Esquema del sistema } !!Num.Proceso! | _ nn1 nl1 nn2 nl2 nnf _ _ _ _ _ ! V | ------------------------------------ | | | | | ! /[ nn1 | K1 | C1 | 0 | 0 | 0 | | u1 | | f1 | ! P0 | ------------------------------------ | | ------- | | ----- | ! \[ nl1 | C1¬ | 0 | 0 | 0 | I1 | | lambda1 | | 0 | ! | ------------------------------------ | . | ------- | = | ----- | ! /[ nn2 | 0 | 0 | K2 | C2 | 0 | | u2 | | f2 | ! P1 | ------------------------------------ | | ------- | | ----- | ! \[ nl2 | 0 | 0 | C2¬ | 0 | I2 | | lambda2 | | 0 | ! | ------------------------------------ | | ------- | | ----- | ! P2 -[ nnf | 0 | I1¬ | 0 | I2¬ | 0 | | v | | 0 | ! | ------------------------------------ | | | | | ! - - - - - - ! A (M x N) x b !!! Notacion: '¬' = transpuesta ! -------- n1 = nn1 + nl1 ! n2 = nn2 + nl2 ! M = N = n1 + n2 + nnf ! _____________________________________________________________________________

! Con la variable 'PetscInt :: imprimir' controlamos si queremos o no ! que se impriman las matrices y vectores en el nodo maestro. Esto es ! util a la hora de trabajar con matrices grandes (para no imprimirlas).

168

KSP :: ksp PC :: pc Mat :: A Vec :: x, b1, b2 PetscInt :: nn1, nn2, nl1, nl2, nnf PetscInt :: n1, n2, m, MG, NG PetscInt :: Istart,Iend, i, j, k, k2, its, imprimir = 1 PetscScalar :: v, cero = 0.0, uno = 1.0, uno_neg = -1.0 PetscScalar :: cuatro = 4.0, dos = 2.0, dos_neg = -2.0 PetscReal :: norma, tol = 1.d-7

PetscMPIInt :: rank, size PetscErrorCode :: ierr integer :: error=2, r integer,parameter :: seed = 836

! Para insertar valores en un vector mediante indexacion local PetscInt,ALLOCATABLE :: gindices(:) ISLocalToGlobalMapping ltog PetscInt :: nl, rstart, rend

! Para prealojar espacio en memoria para la matriz paralela: PetscInt,ALLOCATABLE :: d_nz(:), o_nz(:)

! La siguiente variable es necesaria al utilizar PetscOptionsGetXxx(), a la ! hora de obtener valores con la opcion 'base de datos'. PetscTruth :: flg

! PetscInitialize() automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr)

! Obtenemos numero de procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr)

! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr)

! Con la opcion base de datos -i ..., si el valor es 0 no se imprimira nada ! en el nodo maestro; e.o.c. si lo hara. call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-i', imprimir, flg, ierr)

! Comprobamos que haya exactamente 3 procesadores, si no, abortamos ejecucion if (size /= 3) then if(rank.eq.0) write(6,'(/,A,/)') & "Numero de procesadores necesarios: SOLO 3" stop 10001 end if

! Obtenemos las dimensiones de las submatrices contenidas en un fichero adjunto open(UNIT=10, FILE="dim.txt", STATUS="OLD", ACTION="READ", & IOSTAT=error)

if (error /= 0) then if(rank.eq.0) write(6,'(/,A,/)') & "Error de lectura de fichero 'dimension.txt'" stop 10002 end if

! La primera linea es un comentario, la ignoramos read (10,*) read (10,*) nn1, nn2, nl1, nl2, nnf

! Cerramos el fichero close(10)

169

! Dimensiones de las submatrices diagonales: n1 = nn1 + nl1 n2 = nn2 + nl2

! Dimensiones globales de la matriz paralela: MG = n1 + n2 + nnf NG = MG

! Especificamos el numero de filas de la matriz global que corresponderan ! a cada procesador. Tambien especificamos el numero de no-ceros de la! matriz diagonal y de la offset de cada procesador. Lo ideal es realizar ! un barrido sobre la matriz a tratar y asi obtener el numero exacto, ! aproximadamente, de no-ceros en cada fila de la matriz.

! En funcion del numero de proceso tendremos unas u otras dimensiones select case (rank)

! >>>> Proceso 0 <<<< case (0) m = n1 ALLOCATE (d_nz(m), o_nz(m))

do r=1, nn1 d_nz(r) = n1 ! no-ceros para K1-C1 o_nz(r) = 0 ! resto de elementos son 0 end do

do r=nn1+1, n1 d_nz(r) = nn1 ! no-ceros para C1¬ o_nz(r) = 1 ! Matriz identidad I1, solo '1s' en la diagonal end do

! >>>> Proceso 1 <<<< case (1) m = n2 ALLOCATE (d_nz(m), o_nz(m))

do r=1, nn2 d_nz(r) = n2 ! no-ceros para K2-C2 o_nz(r) = 0 ! resto de elementos son 0 end do

do r=nn2 +1, n2 d_nz(r) = nn2 ! no-ceros para C2¬ o_nz(r) = 1 ! Matriz identidad I2, solo '1s' en la diagonal end do

! >>>> Proceso 2 <<<< case (2) m = nnf ALLOCATE (d_nz(m), o_nz(m))

do r=1, nnf d_nz(r) = 0 ! no-ceros de la diagonal (son todos ceros) o_nz(r) = 1 + 1 ! el resto de elementos son los '1s' de I1¬ e I2¬ end do

end select

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso.!! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. !! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no

170

! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la! siguiente:! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) !

! IMPORTANTE: Como las submatrices diagonales son cuadradas, n=m ! PETSc da un fallo (dificil de detectar) si no especificamos el valor! del numero de columnas locales (n =m), ya que al crear el vector 'x'! especificaremos su dimension LOCAL. Si, por el contrario, especificamos la! dimension global de 'x', entonces podemos decirle a la PETSc que decida el! valor de n de la matriz en la forma n=PETSC_DECIDE.

! Sabemos que M = MG = SUMATORIO(m_proceso_local_i), pero le diremos a la PETSc ! que lo determine en tiempo de ejecucion. call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, m, PETSC_DETERMINE,& NG, 0, d_nz, 0, o_nz, A, ierr) call MatSetFromOptions(A, ierr)

! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED, ierr)

! Ya no se van a utilizar mas los arrays de indicacion de prealojamiento de! memoria, los destruimos. DEALLOCATE(d_nz, o_nz)

! Actualmente, todos los formatos de matrices paralelas PETSc son! particionadas en trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange()

call MatGetOwnershipRange(A, Istart, Iend, ierr)

! Establecemos los valores para los elementos de la matriz con la funcion! rand(), que genera numeros aleatorios. ! Cada procesador necesita insertar solamente los valores de los elementos ! locales que le pertenezcan, pero cualquier elemento no local introducido ! sera enviado al procesador apropiado durante el ensamblado de la matriz. ! Importante poner el valor ADD_VALUES en vez de INSERT_VALUES si mas de un ! procesador interviene a la hora de colocar un valor en un elemento.

! IMPORTANTE: Siempre hemos de referirnos a las filas y las columnas de la! matriz con ordenacion global a la hora de insertar elementos.

call srand(seed)

! MUY IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C! (indices empiezan por '0'). Es por esta razon por la! cual el indice 'j' empieza en '0'.

! Nota: Istart - Iend = {n1 en P0, n2 en P1, nnf en P3}.! IMPORTANTE: Istart tambien empieza por '0' en el procesador maestro! (indexacion segun convenio de C).

! print *, "n1 = ", n1, "n2 = ", n2, "nnf = ", nnf, "MG = NG = ", NG ! print *, "rank = ", rank, "Iend-Istart = ", Iend - Istart ! print *, "rank = ", rank, "Istart = ", Istart, "Iend = ", Iend

171

select case (rank) ! >>>> Proceso 0 <<<<

case (0)

! Insertamos valores para: K1 y C1 do i=Istart, Istart+nn1-1 bK1: do j=0, nn1-1

if(j.eq.i-Istart .AND. j.ne.nn1-1) then v = cuatro

else if (j.eq.i-Istart .AND. j.eq.nn1-1) then v = dos

else if (j.eq.i-Istart+1 .AND. (i-Istart.ge.0 .AND. & i-Istart.le.nn1-2)) then v = dos_neg

else if (j.eq.i-Istart-1 .AND. (i-Istart.ge.1 .AND. & i-Istart.le.nn1-1)) then v = dos_neg

else v = cero end if

call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK1

bC1: do j=nn1, n1-1

!v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1

end do

! Insertamos valores para: C1¬ e I1 ! Si I1 es diagonal, el primer '1' de I1 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2

do i=Istart+nn1, Iend-1 bC1_: do j=0, nn1-1

!v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1_

v = uno_neg if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

! >>>> Proceso 1 <<<< case (1)

! Insertamos valores para: K2 y C2 do i=Istart, Istart+nn2-1 bK2: do j=n1, n1+nn2-1

172

if (j-n1.eq.i-Istart .AND. (j.ne.n1 .AND. j.ne.n1+nn2-1) ) then v = cuatro

else if(j-n1.eq.i-Istart .AND. (j.eq.n1 .OR. j.eq.n1+nn2-1)) then

v = dos

else if (j-n1.eq.i-Istart+1 .AND. (i-Istart.ge.0 .AND. & i .le. Istart+nn1-2)) then v = dos_neg

else if (j-n1.eq.i-Istart-1 .AND. (i-Istart.ge.1 .AND. & i-Istart.le.nn1-1)) then v = dos_neg

else v = cero end if

call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK2

bC2: do j=n1+nn2, n1+n2-1

!v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2

end do

! Insertamos valores para: C2¬ e I2 ! Si I2 es diagonal, el primer '1' de I2 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2

do i=Istart+nn2, Iend-1 bC2_: do j=n1, n1+nn2-1

!v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2_

v = uno_neg if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

! >>>> Proceso 2 <<<< case (2)

! Insertamos valores para: I1¬ e I2¬ k = nn1 k2 = n1 + nn2

v = uno_neg

do i=Istart, Iend-1

call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) call MatSetValues(A, 1, i, 1, k2, v, ADD_VALUES, ierr)

173

k = k + 1 k2 = k2 + 1 end do

end select

! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y! MatAssemblyEnd(). ! Se pueden realizar calculos, que no involucren el uso de la matriz A, en! medio de las dos rutinas mientras se realiza el paso de mensajes.

call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr)

! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(),! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension! global, el particionamiento paralelo es determinado en tiempo de! ejecucion. ! Primero creamos un vector desde cero y, posteriormente, lo! duplicamos. ! Aqui x = x1

call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, m, PETSC_DECIDE, ierr) call VecSetFromOptions(x, ierr)

call VecDuplicate(x, b1, ierr) ! Para resolver con KSP call VecDuplicate(x, b2, ierr) ! Para comprobar con la multiplicacion

! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(b1, rstart, rend, ierr)

! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart

ALLOCATE ( gindices(nl) ) gindices(1) = rstart

do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do

! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1).eq.-1) gindices(1) = NG if ( gindices(nl).eq. NG+1) gindices(nl) = 1

! Realizamos el mapeado de indices locales a globales, de modo que! utilizamos los indices locales para introducir elementos y! automaticamente se introducen en el vector paralelo con su! correspondiente indice global mapeado. !

! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(b1, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr)

DEALLOCATE(gindices)

! CUALQUIER VECTOR DUPLICADO DE 'b1' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO

!! Determinamos ahora los elementos del vector paralelo. !

174

! - Cada procesador determina sus valores locales utilizando ordenacion! local. ! - Cada procesador puede contribuir para cualquier entrada del vector! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas! juntas.

! OJO: VecSetValuesLocal() utiliza indices basados en '0' en Fortran tambien,! como en C.

do i=0, nl-1, 1

! Todos los valores de f1 y f2 son '0', excepto el ultimo elemento ! de f2, que vale '1'. f2 pertenece al procesador 1. v = cero if( rank.eq.1 .AND. i.eq.nn2-1 ) v = uno call VecSetValuesLocal(b1,1,i,v,INSERT_VALUES, ierr) end do

!! Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() ! y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos! rutinas mientras se realiza el paso de mensajes. ! call VecAssemblyBegin(b1, ierr) call VecAssemblyEnd(b1, ierr)

! Visualizamos la matriz A y el vector b1.! Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo ! a un fichero.

if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr)

if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b1: *****'

call VecView(b1, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

! ____________________________________________________________________________

! Resolvemos ahora el sistema lineal mediante KSP

! Creamos el contexto para el solver lineal

call KSPCreate(PETSC_COMM_WORLD, ksp, ierr)

! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve! tambien de matriz de precondicion.

call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr)

! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. !! - Las 3 siguientes sentencias son opcionales; todos estos parametros podrian

175

! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions().

call KSPGetPC(ksp, pc, ierr) call PCSetType(pc, PCJACOBI, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_INTEGER, ierr)

! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> !! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores.

call KSPSetFromOptions(ksp, ierr)

! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b1, x, ierr)

! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). if(rank.eq.0) write(6,'(3/)')

call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr)

! Visualizamos el resultado: if(imprimir.ne.0) then if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x (solucion): *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

!______________________________________________________________________________

! A continuacion realizamos la operacion Ax = b2: call MatMult(A, x, b2, ierr)

! Visualizamos el resultado if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b2 (Ax=b2): *****'

call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

!______________________________________________________________________________

! Chequeamos el error:

call VecAXPY(b2, uno_neg, b1, ierr) call VecNorm(b2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr)

! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if (norma .gt. 1.e-12) then write(6,100) norma, its else write(6,200) its endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /)

176

200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if

! Todos los procesos han de esperar a que el maestro realice el bloque! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr)

! Liberamos la memoria alojada para la matriz, los vectores y el contexto KSP. call KSPDestroy(ksp); call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(b1, ierr) call VecDestroy(b2, ierr)

call PetscFinalize(ierr)

end

!*****************************************************************************!* Fin de 'KSPstruct_Luis.F90' * !*****************************************************************************

177

Ahora, se presenta el código contenido en ‘diag_dom.F90’.

!*****************************************************************************!* * !* Descripcion: Matriz llena con diagonal dominante * !* * !* Primero, resolveremos con KSP el sistema A·x = b1 * !* Posteriormente, realizaremos la multiplicacion A·x y * !* obtendremos b2. * !* Finalmente, comprobaremos que el valor de b1 y b2 es * !* aproximadamente el mismo en ambos casos (viendo la norma * !* del error) * !* * !* Opciones BD: -m : numero de filas por proceso * !* -i : 1=imprimir matriz y vectores, 0=no imprimir (elementos * !* grandes) * !* * !* Numero de procesadores: n * !* * !* Autor: ICR, 15/11/2006 * !* * !*****************************************************************************

program main implicit none

#include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h"

KSP :: ksp PC :: pc Vec :: x, b1, b2 Mat :: A

! especificamos el valor por defecto de 'm' (numero de filas locales de la ! matriz paralela), por si no se especifica como argumento del programa ! (opcion de base de datos). PetscInt :: m = 10, N, Istart, Iend, i, j PetscInt :: nl, rstart, rend

! Con la variable 'PetscInt :: imprimir' controlamos si queremos o no ! que se impriman las matrices y vectores en el nodo maestro. Esto es ! util a la hora de trabajar con matrices grandes (para no imprimirlas). PetscInt :: imprimir = 1, its

PetscInt,ALLOCATABLE :: gindices(:) PetscScalar :: v, uno_neg = -1.0 PetscMPIInt :: rank, size PetscTruth :: flg PetscErrorCode :: ierr

! Declare the type of the rand() function PetscScalar :: rand integer,parameter :: seed = 86456 PetscReal :: norma, tol = 1.d-7 ISLocalToGlobalMapping ltog

178

! Automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr)

! Obtenemos numero procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr)

! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr)

! Consiguimos el valor entero para una opcion particular en la base de datos ! En este caso, 'm' es el numero de filas locales por proceso (todos! mismo numero de filas). call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-m', m, flg, ierr)

! Con la opcion base de datos -i ..., si el valor es 0 no se imprimira nada ! en el nodo maestro; e.o.c. si lo hara. call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-i', imprimir, flg, ierr)

! Numero de columnas (numero global). N = m * size

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso.! La dimension de la submatriz local sera m*N, donde N = m*size (matriz! cuadrada). La dimension de la matriz global sera M*N, donde ! N = M = m*size. !! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la! siguiente:! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, PETSC_DECIDE, & PETSC_DETERMINE, N, m, PETSC_NULL_INTEGER, & N - m, PETSC_NULL_INTEGER, A, ierr) call MatSetFromOptions(A, ierr)

! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED)

!! Actualmente, todos los formatos de matrices paralelas PETSc son! particionadas por trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() ! call MatGetOwnershipRange(A, Istart, Iend, ierr)

! Establecemos los valores para los elementos de la matriz con la funcion! rand(), que genera numeros aleatorios. Cada procesador necesita insertar ! solamente los valores de los elementos locales que le pertenezcan, pero! cualquier elemento no local introducido sera enviado al procesador ! apropiado durante el ensamblado de la matriz.

! Siempre hemos de referirnos a las filas y las columnas de la matriz con ! ordenacion global a la hora de insertar elementos.

179

! In order to get a different sequence each time, we initialize the ! seed of the random number function with the sum of the current ! hour, minute, and second. !! call itime(timeArray) ! Get the current time ! v = rand(timeArray(1) + timeArray(2) + timeArray(3))

call srand(seed)

! IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C! (indices empiezan por '0'). Es por esta razon por la! cual el indice 'j' empieza en '0'.

do i=Istart, Iend-1 do j=0, N-1

v = rand() ! * (rank + 1) if(i-Istart+rank*m .eq. j) v = v * N**2

call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr)

end do end do

!! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y! MatAssemblyEnd(). ! Se pueden realizar calculos en medio de las dos rutinas mientras se! realiza el paso de mensajes. ! call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr)

!! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(),! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension! global, el particionamiento paralelo es determinado en tiempo de! ejecucion. !! El especificar la dimension global viene por la razon de que el vector! sea compatible con las dimensiones de la matriz a la hora de realizar el! producto. Primero creamos un vector desde cero y, posteriormente, lo! duplicamos. ! call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, PETSC_DECIDE, N, ierr) call VecSetFromOptions(x, ierr)

call VecDuplicate(x, b1, ierr) call VecDuplicate(x, b2, ierr)

!! Ahora haremos que cada procesador introduzca valores en su parte local ! del vector paralelo. Para ello, es necesario determinar la ordenacion ! local a global para el vector. Cada procesador genera una lista de indices ! globales para cada indice local.!! Si hubiesemos especificado el tamanyo local del vector en vez del global, ! necesitariamos obtener el tamanyo global de la forma: ! VecGetSize(x, N, ierr) !! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(b1, rstart, rend, ierr)

180

! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart

ALLOCATE ( gindices(nl) )

gindices(1) = rstart

do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do

! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1) .eq. -1) gindices(1) = N if ( gindices(nl) .eq. N+1) gindices(nl) = 1

! Realizamos el mapeado de indices locales a globales, de modo que! utilizamos los indices locales para introducir elementos y! automaticamente se introducen en el vector paralelo con su! correspondiente indice global mapeado. !! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(b1, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr)

DEALLOCATE(gindices)

! CUALQUIER VECTOR DUPLICADO DE 'b1' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO

! Determinamos ahora los elementos del vector paralelo. !! - Cada procesador determina sus valores locales utilizando ordenacion! local. ! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas.

! OJO: VecSetValuesLocal() utiliza indices basados en '0' en Fortran, como en C

do i=0, nl-1, 1 v = rank + 1.0 call VecSetValuesLocal(b1, 1, i, v, INSERT_VALUES, ierr) end do

! Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() ! y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos! rutinas mientras se realiza el paso de mensajes. call VecAssemblyBegin(b1, ierr) call VecAssemblyEnd(b1, ierr)

!! Vemos la matriz y el vector. Omitir los siguientes pasos si son! dimensiones grandes con la opcion -i 0, o imprimir a un fichero. ! if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****'

call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') &

181

' ***** Vector paralelo b1: *****'

call VecView(b1, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

! Resolvemos ahora el sistema lineal mediante KSP

! Creamos el contexto para el solver lineal

call KSPCreate(PETSC_COMM_WORLD, ksp, ierr)

! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve! tambien de matriz de precondicion.

call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr)

! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. !! - Las 3 siguientes sentencias son opcionales; todos estos parametros podrian ! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions().

call KSPGetPC(ksp, pc, ierr) call PCSetType(pc, PCJACOBI, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_INTEGER, ierr)

! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> !! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores.

call KSPSetFromOptions(ksp, ierr)

! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b1, x, ierr)

! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). if(rank.eq.0) write(6,'(3/)')

call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr)

! Visualizamos el resultado: if(imprimir.ne.0) then if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x (solucion): *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

!______________________________________________________________________________

! A continuacion realizamos la operacion Ax = b2: call MatMult(A, x, b2, ierr)

! Visualizamos el resultado

182

if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b2 (Ax=b2): *****'

call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

!______________________________________________________________________________

! Chequeamos el error:

call VecAXPY(b2, uno_neg, b1, ierr) call VecNorm(b2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr)

! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if (norma .gt. 1.e-12) then write(6,100) norma, its else write(6,200) its endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if

! Todos los procesos han de esperar a que el maestro realice el bloque! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr)

! Destruimos la matriz y los vectores paralelos, asi como el contexto KSP call KSPDestroy(ksp); call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(b1, ierr) call VecDestroy(b2, ierr)

call PetscFinalize(ierr)

end

!*****************************************************************************!* Fin de 'diag_dom.F90' * !*****************************************************************************/

183

A continuación, el código fuente contenido en ‘diag_domN.F90’.

!*****************************************************************************!* * !* Descripcion: Matriz llena en disposición de tablero de ajedrez * !* con diagonal dominante (multiplicada por N) * !* * !* Primero, resolveremos con KSP el sistema A·x = b1 * !* Posteriormente, realizaremos la multiplicacion A·x y * !* obtendremos b2. * !* Finalmente, comprobaremos que el valor de b1 y b2 es * !* aproximadamente el mismo en ambos casos (viendo la norma * !* del error) * !* * !* Opciones BD: -m : numero de filas por proceso * !* -i : 1=imprimir matriz y vectores, 0=no imprimir (elementos * !* grandes) * !* * !* Numero de procesadores: n * !* * !* Autor: ICR, 15/11/2006 * !* * !*****************************************************************************

program main implicit none

#include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h"

KSP :: ksp PC :: pc Vec :: x, b1, b2 Mat :: A

! especificamos el valor por defecto de 'm' (numero de filas locales de la ! matriz paralela), por si no se especifica como argumento del programa ! (opcion de base de datos). PetscInt :: m = 10, N, Istart, Iend, i, j, k PetscInt :: nl, rstart, rend

! Con la variable 'PetscInt :: imprimir' controlamos si queremos o no ! que se impriman las matrices y vectores en el nodo maestro. Esto es ! util a la hora de trabajar con matrices grandes (para no imprimirlas). PetscInt :: imprimir = 1, dos = 2, d_nz, o_nz

! Parametros de GMRES PetscInt :: numvec = 100, its, maxits = 10000

PetscInt,ALLOCATABLE :: gindices(:) PetscScalar :: v, uno_neg = -1.0 PetscMPIInt :: rank, size PetscTruth :: flg PetscErrorCode :: ierr

184

PetscScalar :: rand integer,parameter :: seed = 86456 PetscReal :: norma, tol = 1.d-7

ISLocalToGlobalMapping ltog

! Automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr)

! Obtenemos numero procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr)

! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr)

! Consiguimos el valor entero para una opcion particular en la base de datos ! En este caso, 'm' es el numero de filas locales por proceso (todos! mismo numero de filas). call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-m', m, flg, ierr)

! Con la opcion base de datos -i ..., si el valor es 0 no se imprimira nada ! en el nodo maestro; e.o.c. si lo hara. call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-i', imprimir, flg, ierr)

! Numero de columnas (numero global). N = m * size

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso.! La dimension de la submatriz local sera m*N, donde N = m*size (matriz! cuadrada). La dimension de la matriz global sera M*N, donde ! N = M = m*size. !! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la! siguiente:! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr)

! OJO con el prealojamiento: Numero de bloques m*m llenar por fila: !! d_nz = m !! size: par => o_nz = (size/2 -1) * m (el de la diagonal no se cuenta) !! | (size/2) * m si rank par ! size: impar => o_nz = |! | (size/2 -1) * m si rank impar !!! (size/2 es division entera)

d_nz = m

if(mod(size,dos) .eq. 0) then o_nz = (size/2 -1) * m

else if(mod(rank,dos) .eq. 0) then o_nz = (size/2) * m else o_nz = (size/2 -1) * m end if

185

end if

call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, PETSC_DECIDE, & PETSC_DETERMINE, N, d_nz, PETSC_NULL_INTEGER, & o_nz, PETSC_NULL_INTEGER, A, ierr) call MatSetFromOptions(A, ierr)

! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED)

!! Actualmente, todos los formatos de matrices paralelas PETSc son! particionadas por trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() ! call MatGetOwnershipRange(A, Istart, Iend, ierr)

! Establecemos los valores para los elementos de la matriz con la funcion! rand(), que genera numeros aleatorios. Cada procesador necesita insertar ! solamente los valores de los elementos locales que le pertenezcan, pero! cualquier elemento no local introducido sera enviado al procesador ! apropiado durante el ensamblado de la matriz.

! Siempre hemos de referirnos a las filas y las columnas de la matriz con ! ordenacion global a la hora de insertar elementos.

!! In order to get a different sequence each time, we initialize the ! seed of the random number function with the sum of the current ! hour, minute, and second. !! call itime(timeArray) ! Get the current time ! v = rand(timeArray(1) + timeArray(2) + timeArray(3))

call srand(seed)

! IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C! (indices empiezan por '0'). Es por esta razon por la! cual el indice 'j' empieza en '0'.

! Proceso PAR => llenar bloques m*m PARES con numeros aleatorios ! Proceso IMPAR => llenar bloques m*m IMPARES con numeros aleatorios ! Bloques de la diagonal => multiplicar valores por N (dim. global matriz)

! Numeros de procesos = size = numeros de bloques en cada fila

! PROCESOS PARES if(mod(rank,dos) .eq. 0) then

do i=Istart, Iend-1 k = 0 ! k indica el numero de bloque

do j=0, N-1 ! k aumenta con cada m columnas(1 bloque) if(mod(j, m).eq.0 .AND. j.ne.0) k = k +1

! En los procesos pares, llenamos los bloques pares if(mod(k,dos) .eq. 0) then v = rand() ! * (rank + 1) ! Los valores de la diagonal los escalamos con N

186

if(k .eq. rank) v = v * N call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end if end do end do

! PROCESOS IMPARES else do i=Istart, Iend-1 k = 0 ! k indica el numero de bloque

do j=0, N-1 ! k aumenta con cada m columnas(1 bloque) if(mod(j, m).eq.0 .AND. j.ne.0) k = k +1

! En los procesos impares, llenamos los bloques impares if(mod(k,dos) .ne. 0) then v = rand() ! * (rank + 1) ! Los valores de la diagonal los escalamos con N if(k .eq. rank) v = v * N call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end if end do end do end if

! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y! MatAssemblyEnd(). ! Se pueden realizar calculos en medio de las dos rutinas mientras se! realiza el paso de mensajes. ! call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr)

!! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(),! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension! global, el particionamiento paralelo es determinado en tiempo de! ejecucion. !! El especificar la dimension global viene por la razon de que el vector! sea compatible con las dimensiones de la matriz a la hora de realizar el! producto. Primero creamos un vector desde cero y, posteriormente, lo! duplicamos. ! call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, PETSC_DECIDE, N, ierr) call VecSetFromOptions(x, ierr)

call VecDuplicate(x, b1, ierr) call VecDuplicate(x, b2, ierr)

!! Ahora haremos que cada procesador introduzca valores en su parte local ! del vector paralelo. Para ello, es necesario determinar la ordenacion ! local a global para el vector. Cada procesador genera una lista de indices ! globales para cada indice local.!

187

! Si hubiesemos especificado el tamanyo local del vector en vez del global, ! necesitariamos obtener el tamanyo global de la forma: ! VecGetSize(x, N, ierr) !! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(b1, rstart, rend, ierr)

! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart

ALLOCATE ( gindices(nl) )

gindices(1) = rstart

do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do

! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1) .eq. -1) gindices(1) = N if ( gindices(nl) .eq. N+1) gindices(nl) = 1

! Realizamos el mapeado de indices locales a globales, de modo que! utilizamos los indices locales para introducir elementos y! automaticamente se introducen en el vector paralelo con su! correspondiente indice global mapeado. !! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(b1, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr)

DEALLOCATE(gindices)

! CUALQUIER VECTOR DUPLICADO DE 'b1' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO

! Determinamos ahora los elementos del vector paralelo. !! - Cada procesador determina sus valores locales utilizando ordenacion! local. ! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas.

! OJO: VecSetValuesLocal() utiliza indices basados en '0' en Fortran, como en C

do i=0, nl-1, 1 v = rank + 1.0 call VecSetValuesLocal(b1, 1, i, v, INSERT_VALUES, ierr) end do

! Ensamblamos el vector paralelo, utilizando las rutinas VecAssemblyBegin() ! y VecAssemblyEnd(). Se pueden realizar calculos en medio de las dos! rutinas mientras se realiza el paso de mensajes. call VecAssemblyBegin(b1, ierr) call VecAssemblyEnd(b1, ierr)

!! Vemos la matriz y el vector. Omitir los siguientes pasos si son! dimensiones grandes con la opcion -i 0, o imprimir a un fichero. !

188

if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****'

call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b1: *****'

call VecView(b1, PETSC_VIEWER_STDOUT_WORLD, ierr) end if

! Resolvemos ahora el sistema lineal mediante KSP

! Creamos el contexto para el solver lineal

call KSPCreate(PETSC_COMM_WORLD, ksp, ierr)

! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve! tambien de matriz de precondicion.

call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr)

! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. !! - Las 4 siguientes sentencias son opcionales; todos estos parametros podrian ! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions().

call KSPGetPC(ksp, pc, ierr) ! call PCSetType(pc, PCJACOBI, ierr) call PCSetType(pc, PCNONE, ierr)

! call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & ! PETSC_DEFAULT_DOUBLE_PRECISION, & ! PETSC_DEFAULT_INTEGER, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & maxits, ierr)

! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> !! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores.

call KSPSetFromOptions(ksp, ierr)

! Ajustamos el numero de vectores del subespacio con la variable 'restart'. ! Esta sentencia ha de ir despues de la anterior, si no, no realiza nada. call KSPGMRESSetRestart(ksp, numvec, ierr)

! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b1, x, ierr)

! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). if(rank.eq.0) write(6,'(3/)')

189

call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Visualizamos el resultado: ! Visualizamos el resultado: if(imprimir.ne.0) then if(imprimir.ne.0) then if(rank.eq.0) write(6,'(3/,T5,A,/)') & if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x (solucion): *****' ' ***** Vector paralelo x (solucion): *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) end if end if !______________________________________________________________________________!______________________________________________________________________________ ! A continuacion realizamos la operacion Ax = b2: ! A continuacion realizamos la operacion Ax = b2: call MatMult(A, x, b2, ierr) call MatMult(A, x, b2, ierr) ! Visualizamos el resultado ! Visualizamos el resultado if(imprimir.ne.0) then if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b2 (Ax=b2): *****' ' ***** Vector paralelo b2 (Ax=b2): *****' call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) end if end if !______________________________________________________________________________!______________________________________________________________________________ ! Chequeamos el error: ! Chequeamos el error: call VecAXPY(b2, uno_neg, b1, ierr) call VecAXPY(b2, uno_neg, b1, ierr) call VecNorm(b2, NORM_2, norma, ierr) call VecNorm(b2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr) call KSPGetIterationNumber(ksp, its, ierr) ! Esto solamente lo realiza el proceso maestro ! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if(rank.eq.0) then if (norma .gt. 1.e-12) then if (norma .gt. 1.e-12) then write(6,100) norma, its write(6,100) norma, its else else write(6,200) its write(6,200) its endif endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if end if ! Todos los procesos han de esperar a que el maestro realice el bloque! Todos los procesos han de esperar a que el maestro realice el bloque! anterior de sentencias ! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr) call MPI_Barrier(PETSC_COMM_WORLD, ierr) ! Destruimos la matriz y los vectores paralelos, asi como el contexto KSP ! Destruimos la matriz y los vectores paralelos, asi como el contexto KSP call KSPDestroy(ksp); call KSPDestroy(ksp); call MatDestroy(A, ierr) call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(x, ierr) call VecDestroy(b1, ierr) call VecDestroy(b1, ierr) call VecDestroy(b2, ierr) call VecDestroy(b2, ierr) call PetscFinalize(ierr) call PetscFinalize(ierr) end end !*****************************************************************************!*****************************************************************************!* Fin de 'diag_domN.F90' !* Fin de 'diag_domN.F90' !*****************************************************************************/!*****************************************************************************/

190190

191

192