O produto de Kronecker é um tipo especial de produto de matrizes (produto tensorial).
Esse produto é aplicado, por exemplo, no cálculo do laplaciano discreto, que, por sua vez, é utilizado na resolução numérica de algumas equações diferenciais parciais, como a equação de Laplace.
O objetivo desta postagem é apresentar um algoritmo que permite calcular o produto de Kronecker entre duas matrizes.
Além do pseudocódigo, disponibilizarei o algoritmo codificado em Java e em C.
Também mostrarei como calcular o produto de Kronecker no MATLAB e no Octave e um exemplo de equação resolvida numericamente com o auxílio desse produto.
Produto de Kronecker
Se $A_{p\times q}$ é uma matriz com $p$ linhas e $q$ colunas e $B_{r\times s}$ é uma matriz com $r$ linhas e $s$ colunas, então o produto de Kronecker de $A$ e $B$ (denotado por $A\otimes B$) é uma matriz $C_{pr\times qs}$ com $pr$ linhas e $qs$ colunas dividida em $p\times q$ blocos. Cada bloco $(i, j)$ é uma matriz da forma $a_{ij} B$, onde $a_{ij}$ é o elemento da matriz $A$ que ocupa a linha $i$ e coluna $j$ (TREFETHEN, 2000).
Não entendeu nada? Um exemplo deixará mais claro:
Em geral, o produto de Kronecker não é comutativo
$$A\otimes B\neq B\otimes A$$
Além disso, não há restrições em relação às dimensões de A e de B.
$$\begin{bmatrix}2\\4\end{bmatrix}\otimes\begin{bmatrix}2 & 1\\3 & 1\end{bmatrix}=\begin{bmatrix}4 & 2\\6 & 2\\\hline8 & 4\\12 & 4\end{bmatrix}$$
A maneira como os blocos são distribuídos depende da matriz A. Nesse último exemplo, a matriz A tem tamanho 2×1. Portanto, o produto conterá 2×1 blocos. O primeiro bloco (1, 1) é igual ao elemento 2 multiplicado pela matriz B. O segundo bloco (2, 1) é igual ao produto do elemento 4 por B.
Na prática, o produto de Kronecker é mais fácil do que o produto usual de matrizes. Por outro lado, seu algoritmo é um pouco mais complexo.
Os blocos mencionados ao longo do texto são apenas uma maneira de facilitar a compreensão, pois o resultado do produto $A\otimes B$ é uma matriz como qualquer outra.
Algoritmo
O algoritmo do produto de Kronecker é apresentado a seguir
1. kron(Ap×q, Br×s) 2. | inicializar a matriz Cpr×qs 3. | para i ← 1 até p 4. | | para j ← 1 até q 5. | | | row ← (i - 1) * r + 1 6. | | | para k ← até r 7. | | | | col ← (j - 1) * s + 1 8. | | | | para l ← 1 até s 9. | | | | | C(row, col) ← A(i, j) * B(k, l) 10.| | | | | col ← col + 1 11.| | | | fim_para 12.| | | | row ← row + 1 13.| | | fim_para 14.| | fim_para 15.| fim_para 16.| retorne Cpr×qs 17.fim_kron
A complexidade no tempo desse algoritmo é $O(p\times r\times q\times s)$.
Códigos
Os códigos do produto de Kronecker em Java e em C são apresentados a seguir.
Código em Java
//GitHub: HenriqueIni / www.blogcyberini.com public class ProdutoKronecker { //Calcula o produto de Kronecker entre A e B public static double[][] kron(double[][] A, double[][] B){ //O código não faz validações. A e B devem ser matrizes válidas int p = A.length; int q = A[0].length; int r = B.length; int s = B[0].length; int row, col; //Matriz resultado double[][] C = new double[p * r][q * s]; //Laço quádruplo aninhado: percorre as matrizes A e B for(int i = 0; i < p; i++){ for(int j = 0; j < q; j++){ row = i * r; for(int k = 0; k < r; k++){ col = j * s; for(int l = 0; l < s; l++){ //Preenche a matriz C C[row][col] = A[i][j] * B[k][l]; col++; } row++; } } } return C; } // Método auxiliar para transformar matriz em String private static String toString(double[][] matrix){ String aux = ""; for(int i = 0; i < matrix.length; i++){ aux += "[" + matrix[i][0]; for(int j = 1; j < matrix[i].length; j++){ aux += "," + matrix[i][j]; } aux += "]"; if(i != matrix.length - 1){ aux += "\n"; } } return aux; } //Testes public static void main(String[] args) { //Teste 1 double[][] A = {{2,4}}; double[][] B = {{2, 1},{3, 1}}; System.out.println(toString(kron(A, B))+"\n"); //Teste 2 double[][] C = {{1,2}}; double[][] D = {{1,3}}; double[][] E = {{1},{1}}; System.out.println(toString(kron(kron(C, D), E))+"\n"); //Teste 3 double[][] F = {{2},{4}}; System.out.println(toString(kron(F, B))+"\n"); } }
Código em C
Observe que no código em C a matriz que vai conter os resultados deve ser passada nos parâmetros. Se você for mais familiarizado com ponteiros em C (não é o meu caso), então você pode deixar esse algoritmo mais simples ainda.
//GitHub: HenriqueIni / www.blogcyberini.com #include <stdio.h> #include <stdlib.h> //Calcula o produto de Kronecker entre A e B //O resultado é armazenado em C //Esse método pode ser simplificado com o uso de ponteiros void kron(int p, int q, double A[p][q], int r, int s, double B[r][s], int pr, int qs,double C[pr][qs]){ //O código não faz validações, logo os parâmetros devem ser válidos int i, j, k, l, col, row; for(i = 0; i < p; i++){ for(j = 0; j < q; j++){ row = i * r; for(k = 0; k < r; k++){ col = j * s; for(l = 0; l < s; l++){ //Preenche a matriz C C[row][col] = A[i][j] * B[k][l]; col++; } row++; } } } } // Função auxiliar para imprimir matrizes void printArray(int n, int m, double A[n][m]){ int i, j; for(i = 0; i < n; i++){ printf("[%.4f", A[i][0]); for(j = 1; j < m; j++){ printf(",%.4f", A[i][j]); } printf("]"); if(i != n - 1){ printf("\n"); } } } // Testes int main() { //Teste 1 double A[1][2] = {{2,4}}; double B[2][2] = {{2, 1},{3, 1}}; double R[2][4]; kron(1, 2, A, 2, 2, B, 2, 4, R); printArray(2, 4, R); printf("\n\n"); //Teste 2 double F[2][1] = {{2},{4}}; double R2[4][2]; kron(2, 1, F, 2, 2, B, 4, 2, R2); printArray(4, 2, R2); }
MATLAB e Octave
Os softwares MATLAB e Octave já possuem uma função para calcular o produto de Kronecker.
Em ambos os programas, o produto de Kronecker entre as matrizes A
e B
pode ser feito utilizando o seguinte trecho de código [2][3]:
C = kron(A, B)
Laplaciano discreto
Se $D_n^2$ é uma matriz de derivação $n\times n$ de segunda ordem em uma dimensão (isto é, ela serve para calcular a derivada de segunda ordem de uma função de uma variável), então o laplaciano discreto em duas dimensões será [1]
$$L_n=I_n\otimes D_n^2 + D_n^2\otimes I_n$$
$I_n$ é a matriz identidade de ordem $n$. Em três dimensões, teríamos
$$L_n=I_n\otimes I_n\otimes D_n^2 + I_n\otimes D_n^2\otimes I_n + D_n^2\otimes I_n\otimes I_n$$
Nos dois exemplos, estou supondo que uma mesma matriz de derivação é utilizada para derivar em todas as dimensões.
Aplicação
O exemplo a seguir foi retirado do livro "Spectral Methods in MATLAB" por Trefethen (2000).
No exemplo, Trefethen resolve numericamente a equação de Poisson $\nabla^2 u = 10\operatorname{sin}(8x(y - 1))$ no retângulo $[-1,1]\times[-1,1]$, onde a função $u$ é igual a zero na fronteira.
Para resolver a equação, o laplaciano discreto é calculado utilizando o produto de Kronecker.
O programa utilizado nos cálculos pode ser obtido no link: Equação de Poisson (Octave). Eu fiz algumas modificações para que o programa funcionasse no Octave, uma vez que o original foi escrito para MATLAB.
Se quiser uma visão mais ampla desse gráfico, assista ao vídeo: gráfico da solução da equação (YouTube).
Leia também
Referências
- [1] Trefethen, Lloyd. Spectral Methods in MATLAB. SIAM, 2000.
- [2] Octave DOCS: 18.4 Functions of a Matrix. Acesso em 16 de abril de 2018.
- [3] MathWorks: kron. Acesso em 16 de abril de 2018.
Nenhum comentário:
Postar um comentário