Álgebra Linear Computacional básica

Objetivos

  • Associar conceitos abstratos de Álgebra Linear a estruturas computacionais;

  • Realizar operações básicas com vetores e matrizes;

  • Saber como resolver sistemas lineares de pequeno porte;

  • Calcular autovalores e autovetores em matrizes reais;

Introdução

Neste capítulo, mostraremos como realizar operações básicas entre matrizes e vetores usando o computador. A manipulação de matrizes e vetores é essencial em muitas ciências, principalmente para resolver sistemas lineares.

As matrizes são utilizadas na computação para armazenar informações bidimensionais. Em particular, podem representar translação, rotação, escalonamento e sistemas de equações. O estudo da relação entre algoritmos e métodos computacionais para trabalhar eficientemente com matrizes e vetores é realizado no âmbito da Álgebra Linear Computacional.

Matrizes e vetores

Uma matriz \({\bf A}\) de ordem \(m \times n\) pode ser escrita como:

\[\begin{split}{\bf A} = \begin{bmatrix} a_{11} & a_{12} & \ldots & a_{1n} \\ a_{21} & a_{22} & \ldots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \ldots & a_{mn} \end{bmatrix}\end{split}\]

As colunas de uma matriz com \(m\) linhas correspondem a \(n\) vetores \(\vec{v}_1, \vec{v}_2, \ldots,\vec{v}_n\), de maneira que

\[\begin{split}{\bf A} = \begin{bmatrix} \vec{v}_1 & \vec{v}_2 & \ldots & \vec{v}_n \\ \end{bmatrix}\end{split}\]

é uma representação equivalente para a matriz anterior.

Em Python, usamos o módulo numpy para trabalhar com matrizes e vetores. Vetores são arrays 1D, ao passo que matrizes são arrays 2D, ou seja, um “array de arrays”.

Exemplo. Represente computacionalmente os vetores do \(\mathbb{R}^3\) a seguir:

  • \(\vec{u} = 3\vec{i} - 2\vec{j} + 9\vec{k}\)

  • \(\vec{v} = -2\vec{i} + 4\vec{j}\)

  • \(\vec{w} = \vec{i}\)

Note

O NumPy possui uma classe especial para se trabalhar com matrizes e vetores em uma ou duas dimensões, a saber o tipo matrix, ou mat. Com objetos matrix, as operações particulares de multiplicação matriz-matriz ou matriz-vetor comportam-se diferentemente daquelas na classe ndarray. Neste texto, abordaremos apenas os tipos ndarray porque são aplicáveis também a matrizes multidimensionais.

import numpy as np 

u = np.array([3,-2,9])
v = np.array([-2,4,0])
w = np.array([1,0,0])
print(u), print(v), print(w);
[ 3 -2  9]
[-2  4  0]
[1 0 0]

Exemplo. Represente computacionalmente a matriz 3 x 3 dada por

\[\begin{split}{\bf A} = \begin{bmatrix} \vec{u} & \vec{v} & \vec{w} \\ \end{bmatrix}\end{split}\]

Observe que os vetores devem ser escritos como “coluna”.

A = np.array([[3,-2,1],[-2,4,0],[9,0,0]])
print(A)
[[ 3 -2  1]
 [-2  4  0]
 [ 9  0  0]]

Exemplo. Represente computacionalmente a matriz

\[\begin{split} \begin{bmatrix} 2 & -2 \\ 4 & 1 \\ 2 & 1 \end{bmatrix}\end{split}\]

Vamos escrever linha por linha.

L1 = np.array([2,-2]) # linha 1
L2 = np.array([4,1]) # linha 2
L3 = np.array([2,1]) # linha 3

A2 = np.array([L1,L2,L3]) # lista de listas
print(A2)
[[ 2 -2]
 [ 4  1]
 [ 2  1]]

Diretamente, poderíamos também definir:

A3 = np.array([[2,-2],[4,1],[2,1]])
print(A3)
[[ 2 -2]
 [ 4  1]
 [ 2  1]]

Note que cada lista representa uma linha.

Transposição

Matrizes e vetores podem ser transpostos com .T:

A2T = A2.T
print(A2T)
[[ 2  4  2]
 [-2  1  1]]

Assim, com as variáveis antes definidas, poderíamos, equivalentemente, fazer para \({\bf A}\):

# modo 2: matriz transposta
At = np.array([u,v,w]).T 
print(At)
[[ 3 -2  1]
 [-2  4  0]
 [ 9  0  0]]

Teste de igualdade

Podemos verificar a igualdade entre matrizes como

A == At
array([[ True,  True,  True],
       [ True,  True,  True],
       [ True,  True,  True]])

No caso de vetores:

# vetor "linha" não difere
# do vetor "coluna"
u == u.T 
array([ True,  True,  True])

Operações fundamentais

Adição e subtração

A adição (subtração) de matrizes e vetores pode ser realizada de modo usual com computação vetorizada.

Exemplo: \(\vec{u} \pm \vec{v}\)

# adição 
ad = u + v
print(ad)

# subtração
sub = u - v
print(sub)
[1 2 9]
[ 5 -6  9]

Exemplo: \(\bf{A} \pm \bf{B}\), com

\[\begin{split}{\bf B} = \begin{bmatrix} \vec{u} & 2\vec{u} & 3\vec{v} \\ \end{bmatrix}\end{split}\]
# adição

B = np.array([u,2*u,3*v]).T

ad2 = A + B
print(ad2)

sub2 = A - B
print(sub2)
[[ 6  4 -5]
 [-4  0 12]
 [18 18  0]]
[[  0  -8   7]
 [  0   8 -12]
 [  0 -18   0]]

Produto interno

O produto interno \(\langle \vec{u}, \vec{v}\rangle\) é computado com .dot:

pi = np.dot(u,v)
print(pi)

pi2 = np.dot(np.array([3,1]),np.array([-1,-1]))
print(pi2)
-14
-4

Norma de vetor

A norma \(||\vec{u}||\) de um vetor \(\vec{u}\) é calculada como:

np.sqrt(np.dot(u,u))
9.695359714832659

Produto de matrizes

O produto \(\bf{A}\bf{B}\) entre matrizes bidimensionais pode ser calculado com np.dot, mas recomenda-se usar np.matmul.

# não tem o mesmo efeito para 
# matrizes A e B de tamanhos arbitrários
np.dot(A,B)
array([[ 22,  44, -42],
       [-14, -28,  60],
       [ 27,  54, -54]])
# uso recomendado para a operação tradicional
np.matmul(A,B)
array([[ 22,  44, -42],
       [-14, -28,  60],
       [ 27,  54, -54]])

Produto entre matriz e vetor

Neste caso, sendo \({\vec{\vec A}}\) (dois símbolos indicam que a matriz é uma grandeza de ordem 2, ao passo que o vetor é de ordem 1 e aqui usamos para consistência de notação) e \({\vec{b}}\) uma matriz \(m \times n\) e um vetor \(n \times 1\), respectivamente, o produto \(\vec{\vec{A}}\vec{b}\) é dado por:

b = np.array([3,4,1])

np.dot(A,b)
array([ 2, 10, 27])

Demais operações com numpy.linalg

Para outras operações, devemos utilizar o submódulo numpy.linalg. Para importá-lo com o alias lin, fazemos:

import numpy.linalg as lin

Determinante

O determinante de \({\bf A}\) é dado por \(\det({\bf A})\) e pode ser computado pela função det.

# calculando o determinante da matriz
det = lin.det(A)
print(det)
-36.0

Inversa de uma matriz

A inversa de uma matriz é dada por \({\bf A}^{-1}\), onde \({\bf A}{\bf A}^{-1}={\bf I}\), e \({\bf I}\) é a matriz identidade. Para usar esta função, devemos fazer:

B2 = np.array([[1,2,3],
              [2,3,4],
              [1,2,0]]) 

B3 = lin.inv(B2)
print(np.matmul(B3,B2))
[[ 1.00000000e+00 -5.55111512e-16  0.00000000e+00]
 [ 1.11022302e-16  1.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  1.00000000e+00]]

Soluções de sistemas lineares

Para resolver sistemas lineares, devemos escrever as equações em forma de matriz, ou seja, \({\vec{\vec A}}\vec{x} = \vec{b}\) e utilizar solve.

Exemplo: Resolva o sistema linear abaixo:

\[\begin{split}\begin{cases} ax + by = c \\ dx + ey = f \end{cases}\end{split}\]

para \(a = -4\), \(b = 1\), \(c = 1/2\), \(d = 3\), \(e = 5\) e \(f = 10\),

A = np.array([[-4,1],[1/2,3]])

b = np.array([5,10]).T

# solução
x = lin.solve(A, b)
print(x)
[-0.4  3.4]

Inversa de matriz

A inversa de uma matriz (faça esta operação apenas para matrizes quadradas de pequena dimensão) pode ser encontrada como:

Ainv = lin.inv(A)
print(Ainv)
[[-0.24  0.08]
 [ 0.04  0.32]]

Para realizar uma “prova real” da solução do sistema anterior, poderíamos fazer:

x2 = np.dot(lin.inv(A), b)
print(x2)
[-0.4  3.4]

Note, entretanto que:

x == x2
array([False, False])

Isto ocorre devido a erros numéricos. Um teste mais adequado deve computar a norma do vetor “erro”, dado por \({\bf e} = \bf{b} - \bf{A}\bf{x}\). A norma pode ser calculada diretamente com:

e = b - np.dot(A,x)
lin.norm(e)
0.0

Isto é, esperamos que \(||{\bf e}|| \approx 0\) quando a solução do sistema for exata, a menos de erros numéricos.

Warning

Nunca compare dois números reais (float) usando igualdade. Ou seja, x == y, não é, em geral, um bom teste lógico para verificar se x e y possuem o mesmo valor numérico.

Algumas matrizes especiais

Nula

Para criar uma matriz nula de ordem m x n, usamos zeros.

m,n = 3,4
np.zeros((m,n))
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

Identidade

Uma matriz identidade (quadrada) de ordem p é criada com eye.

p = 4
np.eye(p)
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])

Matriz de “uns”

Uma matriz composta apenas de valores 1 de ordem m x n pode ser criada com ones:

np.ones((3,5))
array([[1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1.]])

Triangular inferior

A matriz triangular inferior de uma dada matriz pode ser criada com tril. Note que podemos também defini-la explicitamente, linha a linha.

# os valores correspondentes
# são zerados
np.tril(B)
array([[ 3,  0,  0],
       [-2, -4,  0],
       [ 9, 18,  0]])

Triangular superior

A matriz triangular superior de uma dada matriz pode ser criada com triu. Note que podemos também defini-la explicitamente, linha a linha.

np.triu(B)
array([[ 3,  6, -6],
       [ 0, -4, 12],
       [ 0,  0,  0]])

Exercício. Por que há dois valores False no teste a seguir?

B == np.tril(B) + np.triu(B)
array([[False,  True,  True],
       [ True, False,  True],
       [ True,  True,  True]])

Autovalores e autovetores

Um vetor \({\bf v} \in V\), \({\bf v} \neq {\bf 0}\) é vetor próprio de \({\bf A}\) se existir \(\lambda \in \mathbb{R}\) tal que

\[{\bf Av}=\lambda {\bf v}.\]

O número real \(\lambda\) é denominado valor próprio (autovalor) de \({\bf A}\) associado ao vetor próprio (autovetor) \({\bf v}\).

A = np.array([[2,1],
              [1,-5]])

w, v = lin.eig(A)
a,b = w

# autovalores
print(a,b)

# autovetor 1
print(v[:,0])

# autovetor 2
print(v[:,1])
2.1400549446402595 -5.1400549446402595
[0.99033427 0.13870121]
[-0.13870121  0.99033427]

Somas e valores extremos

Podemos calcular somas de elementos de matrizes e vetores de maneiras diferentes. Para matrizes, em particular, há soma total, por linha, ou por coluna.

a = np.array([1,-2,-3,10])

# soma de todos os elementos 
np.sum(a)
6
# modo alternativo
a.sum() 
6
# soma total de matriz
O = np.ones((5,3))

np.sum(O)
15.0
# modo alternativo
O.sum()
15.0
# soma por linha 
np.sum(O,axis=0)
array([5., 5., 5.])
# soma por coluna 
np.sum(O,axis=1)
array([3., 3., 3., 3., 3.])

Valores máximos e mínimos, absolutos ou não, também podem ser computados com funções simples.

# min
np.min(a)
-3
# max
np.max(a)
10
# modo alternativo
a.min()
-3
a.max()
10
# mínimo absoluto 
np.abs(a).min()
1
# máximo absoluto
np.abs(a).max()
10
O2 = np.array([[-4,5],[2,7]])

# min
np.min(O2)
-4
# max 
np.max(O2)
7
O2.min()
-4
O2.max()
7
np.abs(O2).min()
2
np.abs(O2).max()
7