| | | | |

2.9 Decomposição LU

Ajude a manter o site livre, gratuito e sem propagandas. Colabore!

[[badge:remoção]]

Nesta seção, vamos discutir sobre a paralelização da decomposição LU para matrizes. A decomposição LU de uma matriz A com dimensões n×n é

A=LU (2.24)

onde L é uma matriz triangular inferior e U é uma matriz triangular superior, ambas com dimensões n×n.

Para fixar as ideais, vamos denotar A=[ai,j]i,j=0n1, L=[li,j]i,j=0n1 sendo li,i=1 e li,j=0 para i>j, e U=[ui,j]i,j=0n sendo ui,j=0 para i<j. O pseudoalgoritmo serial para computar a decomposição LU é

  1. 1.

    U = A, L = I

  2. 2.

    Para k=0,,n2

    1. (a)

      Para i=k+1,,n1

      1. i.

        li,k=ui,k/uk,k

      2. ii.

        Para j=k,,n1

        1. A.

          ui,j=ui,jli,kuk,j

A forma mais fácil de paralelizar este algoritmo em uma arquitetura MP é paralelizando um de seus laços (itens 2., 2.(a) ou 2.(a)ii.). O laço do item 2. não é paralelizável, pois a iteração seguinte depende do resultado da iteração imediatamente anterior. Agora, os dois laços seguintes são paralelizáveis. Desta forma, o mais eficiente é paralelizarmos o segundo laço 2.(a).

O seguinte código é uma versão paralela da decomposição LU. A matriz A é inicializada como uma matriz simétrica de elementos randômicos (linhas 19-41), sendo que a decomposição é computada nas linhas 43-61.

Código: parallelLU.cc
1#include <omp.h>
2#include <stdio.h>
3#include <ctime>
4#include <algorithm>
5
6#include <gsl/gsl_matrix.h>
7#include <gsl/gsl_vector.h>
8#include <gsl/gsl_rng.h>
9#include <gsl/gsl_blas.h>
10
11int main(int argc, char *argv[]) {
12
13  int n = 5;
14
15  gsl_matrix *a = gsl_matrix_alloc(n,n);
16  gsl_matrix *u = gsl_matrix_alloc(n,n);
17  gsl_matrix *l = gsl_matrix_alloc(n,n);
18
19  // gerador randomico
20  gsl_rng *rng = gsl_rng_alloc(gsl_rng_default);
21  gsl_rng_set(rng, time(NULL));
22
23  // inicializacao
24  printf('Inicializando ... \n');
25  for (int i=0; i<n; i++) {
26    for (int j=0; j<i; j++) {
27      int sig = 1;
28      if (gsl_rng_uniform(rng) >= 0.5)
29        sig = -1;
30      gsl_matrix_set(a, i, j,
31                     sig*gsl_rng_uniform(rng));
32      gsl_matrix_set(a, j, i,
33                     gsl_matrix_get(a, i, j));
34    }
35    int sig = 1;
36    if (gsl_rng_uniform(rng) >= 0.5)
37      sig = -1;
38    gsl_matrix_set(a, i, i,
39                     sig*gsl_rng_uniform_pos(rng));
40  }
41  printf('feito.\n');
42
43  // U = A
44  gsl_matrix_memcpy(u,a);
45  // L = I
46  gsl_matrix_set_identity(l);
47
48  for (int k=0; k<n-1; k++) {
49    #pragma omp parallel for
50    for (int i=k+1; i<n; i++) {
51      gsl_matrix_set(l, i, k,
52                     gsl_matrix_get(u, i, k)/
53                     gsl_matrix_get(u, k, k));
54      for (int j=k; j<n; j++) {
55        gsl_matrix_set(u, i, j,
56                       gsl_matrix_get(u, i, j) -
57                       gsl_matrix_get(l, i, k) *
58                       gsl_matrix_get(u, k, j));
59      }
60    }
61  }
62
63  gsl_matrix_free(a);
64  gsl_matrix_free(u);
65  gsl_matrix_free(l);
66  gsl_rng_free(rng);
67
68  return 0;
69}

2.9.1 Exercícios Resolvidos

Em remoção

ER 2.9.1.

Faça um código MP para computar a solução de um sistema linear Ax=b usando a decomposição LU. Assuma A uma matriz simétrica n×n de elementos randômicos, assim como os elementos do vetor b.

Solução.

A decomposição LU da matriz A nos fornece as matrizes L (matriz triangular inferior) e U (matriz triangular superior), com

A=LU (2.25)

Logo, temos

Ax=b (2.26)
(LU)x=b (2.27)
L(Ux)=b (2.28)

Denotando Ux=y, temos que y é solução do sistema triangular inferior

Ly=b (2.29)

e, por conseguinte, x é solução do sistema triangular superior

Ux=y. (2.30)

Em síntese, o sistema Ax=b pode ser resolvido com o seguinte pseudocódigo:

  1. 1.

    Computar a decomposição LU, A=LU.

  2. 2.

    Resolver Ly=b.

  3. 3.

    Resolver Ux=b.

Cada passo acima pode ser paralelizado. O código MP fica de exercício, veja E 2.9.1.

ER 2.9.2.

Considere a decomposição LU de uma matriz A n×n. Em muitas aplicações, não há necessidade de guardar a matriz A em memória após a decomposição. Além disso, fixando-se que a diagonal da matriz L tem todos os elementos iguais a 1, podemos alocar seus elementos não nulos na parte triangular inferior (abaixo da diagonal) da própria matriz A. E, a matriz U pode ser alocada na parte triangular superior da matriz A. Faça um código MP para computar a decomposição LU de uma matriz A, alocando o resultado na própria matriz A.

Solução.

O seguinte código faz a implementação pedida. Neste código, é necessário alocar apenas a matriz A, sem necessidade de locar as matrizes L e U. Da linha 17 à 39, apenas é gerada a matriz randômica A. A decomposição é computada da linha 41 a 54.

Código: parallelLU2.cc
1#include <omp.h>
2#include <stdio.h>
3#include <ctime>
4#include <algorithm>
5
6#include <gsl/gsl_matrix.h>
7#include <gsl/gsl_vector.h>
8#include <gsl/gsl_rng.h>
9#include <gsl/gsl_blas.h>
10
11int main(int argc, char *argv[]) {
12
13  int n = 5;
14
15  gsl_matrix *a = gsl_matrix_alloc(n,n);
16
17  // gerador randomico
18  gsl_rng *rng = gsl_rng_alloc(gsl_rng_default);
19  gsl_rng_set(rng, time(NULL));
20
21  // inicializacao
22  printf('Inicializando ... \n');
23  for (int i=0; i<n; i++) {
24    for (int j=0; j<i; j++) {
25      int sig = 1;
26      if (gsl_rng_uniform(rng) >= 0.5)
27        sig = -1;
28      gsl_matrix_set(a, i, j,
29                     sig*gsl_rng_uniform(rng));
30      gsl_matrix_set(a, j, i,
31                     gsl_matrix_get(a, i, j));
32    }
33    int sig = 1;
34    if (gsl_rng_uniform(rng) >= 0.5)
35      sig = -1;
36    gsl_matrix_set(a, i, i,
37                     sig*gsl_rng_uniform_pos(rng));
38  }
39  printf('feito.\n');
40
41  for (int k=0; k<n-1; k++) {
42    #pragma omp parallel for
43    for (int i=k+1; i<n; i++) {
44      gsl_matrix_set(a, i, k,
45                     gsl_matrix_get(a, i, k)/
46                     gsl_matrix_get(a, k, k));
47      for (int j=k+1; j<n; j++) {
48        gsl_matrix_set(a, i, j,
49                       gsl_matrix_get(a, i, j) -
50                       gsl_matrix_get(a, i, k) *
51                       gsl_matrix_get(a, k, j));
52      }
53    }
54  }
55  gsl_matrix_free(a);
56  gsl_rng_free(rng);
57
58  return 0;
59}

Este algoritmo demanda substancialmente menos memória computacional que o código parallelLU.cc visto acima. Por outro lado, ele é substancialmente mais lento, podendo demandar até o dobro de tempo. Verifique!

O aumento no tempo computacional se deve ao mau uso da memória cache dos processadores. A leitura de um elemento da matriz, aloca no cache uma sequência de elementos próximos na mesma linha. Ao escrever em um destes elementos, a alocação do cache é desperdiçada, forçando o cache a ser atualizado. Note que o código parallelLU.cc requer menos atualizações do cache que o código parallelLU2.cc.

2.9.2 Exercícios

Em remoção

E. 2.9.1.

Implemente o código MP discutido no ER 2.9.1.

E. 2.9.2.

Implemente um código MP para computar a inversa de uma matriz simétrica de elementos randômicos usando decomposição LU.

E. 2.9.3.

Considere o pseudoalgoritmo serial da composição LU apresentado acima. Por que é melhor paralelizar o laço 2.(a) do que o laço o 2.(a)ii.?

E. 2.9.4.

Use o código MP discutido no ER 2.9.2 para resolver um sistema Ax=b de n equações e n incógnitas. Assuma que a matriz A seja simétrica.

E. 2.9.5.

Um algoritmo paralelo mais eficiente para computar a decomposição LU pode ser obtido usando-se a decomposição LU por blocos. Veja o vídeo https://youtu.be/E8aBJsC0bY8 e implemente um código MP para computar a decomposição LU por blocos.


Envie seu comentário

As informações preenchidas são enviadas por e-mail para o desenvolvedor do site e tratadas de forma privada. Consulte a Política de Uso de Dados para mais informações. Aproveito para agradecer a todas/os que de forma assídua ou esporádica contribuem enviando correções, sugestões e críticas!