198812 Session 2: Matrix algebra
Math to code mapping.
The purpose of the document it to show how mathematical conpects learned in the Computer Programming Prerequisites course could be implemented in a programming language. This supplementary material should help you to transfer theoretical concepts to programming. If you have programming skills you may use the material during the course. If not, just come back to it, once you are advanced enough in programming.
- This document is basesd on the lecture notes by Sébastien Court. It structured in the same way as the lecture notes. It consists only of sections, where numeric examples were given.
- It provides code in Python and R for examples used there. The code serves only demonstration purposes to show a possible implementation. We assumed the correctness of input data.
- For multiple examples of the same calculation, only one is taken. You can easily adapt code for missing calculations.
For this session, code in basic Python, Python with NumPy package and in R is given. Vector and matrix algebra is relatively easy to implement using numpy
(some courses from module 3) or R (198803). In most of the cases just by calling dedicated functions. In basic Python (198801), the required functionality in many cases must be implemented from scratch.
1 Definition and basic operations
Defining an example matrix.
\[ A = \begin{pmatrix} 7.9 & 3.2 & 6 \\ 1.3 & 0 & -4 \\ \end{pmatrix} \]
We define it as a list of lists:
= [[7.9, 3.2, 6],[1.3, 0, -4]]; a a
[[7.9, 3.2, 6], [1.3, 0, -4]]
We define it a an array:
import numpy
= numpy.array(a) A
A =
array([[ 7.9, 3.2, 6. ],
[ 1.3, 0. , -4. ]])
shape = (2, 3)
We define it as matrix:
<- matrix(data = c(7.9, 3.2, 6,1.3, 0, -4), nrow = 2, byrow = TRUE) a
[,1] [,2] [,3]
[1,] 7.9 3.2 6
[2,] 1.3 0.0 -4
Basic Operations
We take the addition as an example operation.
\[ \begin{pmatrix} 2 & 3 \\ 0 & -6 \\ \end{pmatrix} + \begin{pmatrix} -2.5 & 0 \\ 12.3 & -7.4 \\ \end{pmatrix} = \begin{pmatrix} -0.5 & 3 \\ 12.3 & -13.4 \\ \end{pmatrix} \]
We need a nested for loop to add two matrices.
a
[[2, 3], [0, -6]]
b
[[-2.5, 0], [12.3, -7.4]]
a + b =
= len(a)
nrow = len(a[0])
ncol
= []
result for i in range(nrow):
= []
row for j in range(ncol):
+ b[i][j])
row.append(a[i][j]
result.append(row) result
[[-0.5, 3], [12.3, -13.4]]
We just add two arrays with +
or the numpy.add()
function:
A
array([[ 2, 3],
[ 0, -6]])
B
array([[-2.5, 0. ],
[12.3, -7.4]])
+ B A
array([[ -0.5, 3. ],
[ 12.3, -13.4]])
numpy.add(A, B)
array([[ -0.5, 3. ],
[ 12.3, -13.4]])
We define it as matrix:
a
[,1] [,2]
[1,] 2 3
[2,] 0 -6
b
[,1] [,2]
[1,] -2.5 0.0
[2,] 12.3 -7.4
+ b a
[,1] [,2]
[1,] -0.5 3.0
[2,] 12.3 -13.4
2 Subsetting and notation
2.1-3 Subsetting notation
In math and programming languages we may access single elements of composite data. The notation on how to do it depends on the language.
Matrix:
\[ A = \begin{pmatrix} 3 & 6 & 0 \\ 8 & 7 & 1 \\ \end{pmatrix} \]
Elements:
\[ \begin{matrix} a_{11} = 3, & a_{12} = 5, & a_{13} = 0 \\ a_{21} = 8, & a_{22} = 7, & a_{23} = 1 \\ \end{matrix} \]
Matrix (list of lists):
a
[[3, 6, 0], [8, 7, 1]]
Elements:
a[0][0] = 3 a[0][1] = 6 a[0][2] = 0
a[1][0] = 8 a[1][1] = 7 a[1][2] = 1
Matrix (array):
A
array([[3, 6, 0],
[8, 7, 1]])
Elements:
A[0, 0] = 3 A[0, 1] = 6 A[0, 2] = 0
A[1, 0] = 8 A[1, 1] = 7 A[1, 2] = 1
An array may be accessed the same way as a list, too.
A[0][0] = 3 A[0][1] = 6 A[0][2] = 0
A[1][0] = 8 A[1][1] = 7 A[1][2] = 1
Matrix:
a
[,1] [,2] [,3]
[1,] 3 6 0
[2,] 8 7 1
Elements:
a[1, 1] = 3 a[1, 2] = 6 a[1, 3] = 0
a[2, 1] = 8 a[2, 2] = 7 a[2, 3] = 1
2.4 Subsetting sub-matrices
In math and programming languages we may access parts of composite data.
\[ \begin{eqnarray*} A[2, 1] & = & 8 &&& \text{- a single element} \\ A[:,1] & = & \displaystyle \left(\begin{matrix}3 \\ 8\end{matrix}\right) &&& \text{- a submatrix, the first column} \\ A[2,:] & = & (8, 7, 1) &&& \text{- a submatrix, the second row}\\ A[:,(1, 3)] & = & \displaystyle \left(\begin{matrix}3 & 0 \\ 8 & 1 \end{matrix}\right) &&& \text{- a submatrix}\\ \end{eqnarray*} \]
Matrix (list of lists):
a
[[3, 6, 0], [8, 7, 1]]
Selecting a column must be done programmatically, the shortest is via the list comprehension.
0] for row in a] [row[
[3, 8]
Selecting a row with indexing.
1] a[
[8, 7, 1]
Selecting a submatrix must be done programmatically, too.
0], row[2]] for row in a] [[row[
[[3, 0], [8, 1]]
Matrix (array):
A
array([[3, 6, 0],
[8, 7, 1]])
Selecting a column
0] A[:,
array([3, 8])
Selecting a row
1, :] A[
array([8, 7, 1])
Selecting a submatrix
0, 2]] A[:, [
array([[3, 0],
[8, 1]])
Matrix:
a
[,1] [,2] [,3]
[1,] 3 6 0
[2,] 8 7 1
Selecting a column
1] a[,
[1] 3 8
Selecting a row
2, ] a[
[1] 8 7 1
Selecting a submatrix
c(1, 3)] a[,
[,1] [,2]
[1,] 3 0
[2,] 8 1
3 The product Matrix/Vector
3.1-4 … In any dimension
\[ \begin{eqnarray*} & & A = \left( \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \right), \quad X = \left( \begin{matrix} -2 \\ 1 \\ 0 \end{matrix} \right), \\[5pt] & & AX = \left( \begin{matrix} 1\times (-2) + 2\times 1 + 3 \times 0\\ 4\times (-2)+ 5\times 1 + 6 \times 0 \\ 7\times (-2) + 8 \times 1 + 9 \times 0 \end{matrix} \right) = \left( \begin{matrix} 0 \\ -3 \\ -6 \end{matrix} \right). \end{eqnarray*} \]
a
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
x
[-2, 1, 0]
a * x =
= len(x)
ncol = len(a)
nrow = [0] * ncol
result
for i in range(nrow):
for j in range(ncol):
+= a[i][j] * x[j]
result[i]
result
[0, -3, -6]
A
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
X
array([-2, 1, 0])
numpy.dot(A, X)
array([ 0, -3, -6])
a
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
[3,] 7 8 9
x
[1] -2 1 0
%*% x a
[,1]
[1,] 0
[2,] -3
[3,] -6
4 The product Matrix/Matrix
4.1 Definition
\[ \begin{eqnarray*} & & A = \left( \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \right), \quad B = \left( \begin{matrix} -2 & 5 & -1 \\ 1 & 0 & -3 \\ 7 & 0 & -9 \end{matrix} \right), \\[5pt] & & AB = \left( \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix} \right) \left( \begin{matrix} -2 & 5 & -1 \\ 1 & 0 & -3 \\ 7 & 0 & -9 \end{matrix} \right) = \left( \begin{matrix} 21 & 5 & -34 \\ 39 & 20 & -73 \\ 57 & 35 & -112 \end{matrix} \right). \end{eqnarray*} \]
a
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b
[[-2, 5, -1], [1, 0, -3], [7, 0, -9]]
As we will need this code for later examples, we will define our multiply()
function.
def multiply(a, b):
= len(a)
nrow_a = len(b)
nrow_b = len(b[0])
ncol_b
# preparing the result matrix filled with 0
= []
result = [0] * ncol_b
zeros for _ in range(nrow_a):
result.append(zeros[:])
# a * b
for i in range(nrow_a):
for j in range(ncol_b):
for k in range(nrow_b):
+= a[i][k] * b[k][j]
result[i][j]
return result
multiply(a, b)
[[21, 5, -34], [39, 20, -73], [57, 35, -112]]
A
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
numpy.dot(A, B)
array([[ 21, 5, -34],
[ 39, 20, -73],
[ 57, 35, -112]])
a
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
[3,] 7 8 9
b
[,1] [,2] [,3]
[1,] -2 5 -1
[2,] 1 0 -3
[3,] 7 0 -9
%*% b a
[,1] [,2] [,3]
[1,] 21 5 -34
[2,] 39 20 -73
[3,] 57 35 -112
5 Inversion of a matrix
5.1 The identity matrix
\[ \begin{eqnarray*} I_3 & = & \left( \begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{matrix} \right) \end{eqnarray*} \]
We may either define the required identity matrix manually or write a for loop to create it.
1, 0, 0], [0, 1, 0], [0, 0, 1]] [[
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
= 3
n = []
result for i in range(n):
= []
row for j in range(n):
int(i == j))
row.append(
result.append(row) result
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]
3, dtype=int) numpy.identity(
array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
diag(3)
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
5.2 Invertible matrices
For
\[ \begin{eqnarray*} A = \left( \begin{matrix} 4 & 1 \\ -1 & -0.2 \end{matrix} \right), & & B = \left( \begin{matrix} -1 & -5 \\ 5 & 20 \end{matrix} \right) \end{eqnarray*} \]
one can verify that
\[ \begin{equation*} AB \quad = \quad BA \quad = \quad I_2. \end{equation*} \]
a
[[4, 1], [-1, -0.2]]
b
[[-1, -5], [5, 20]]
multiply(a, b)
[[1, 0], [0.0, 1.0]]
multiply(b, a)
[[1, 0.0], [0, 1.0]]
A
array([[ 4. , 1. ],
[-1. , -0.2]])
B
array([[-1, -5],
[ 5, 20]])
numpy.dot(A, B)
array([[ 1.00000000e+00, 0.00000000e+00],
[-5.55111512e-17, 1.00000000e+00]])
numpy.dot(B, A)
array([[1.00000000e+00, 5.55111512e-17],
[0.00000000e+00, 1.00000000e+00]])
Due to impreciseness we have insignificant difference of the obrained and expected results. Let us just round the obtained results.
=15) numpy.around(numpy.dot(A, B), decimals
array([[ 1., 0.],
[-0., 1.]])
=15) numpy.around(numpy.dot(B, A), decimals
array([[1., 0.],
[0., 1.]])
a
[,1] [,2]
[1,] 4 1.0
[2,] -1 -0.2
b
[,1] [,2]
[1,] -1 -5
[2,] 5 20
%*% b a
[,1] [,2]
[1,] 1 0
[2,] 0 1
%*% a b
[,1] [,2]
[1,] 1 0
[2,] 0 1
6 Other products
6.1 Componentwise
\[ \begin{eqnarray*} & & A = \left(\begin{matrix} 3 & -1 \\ -4 & 5 \end{matrix} \right), \quad B = \left(\begin{matrix} 0 & 5 \\ -1 & 7 \end{matrix} \right), \quad A \ast B = \left(\begin{matrix} 0 & -5 \\ 4 & 35 \end{matrix} \right). \end{eqnarray*} \]
a
[[3, -1], [-4, -5]]
b
[[0, 5], [-1, 7]]
a * b =
= len(a)
nrow = len(a[0])
ncol
= []
result for i in range(nrow):
= []
row for j in range(ncol):
*b[i][j])
row.append(a[i][j]
result.append(row)
result
[[0, -5], [4, -35]]
A
array([[ 3, -1],
[-4, -5]])
B
array([[ 0, 5],
[-1, 7]])
* B A
array([[ 0, -5],
[ 4, -35]])
a
[,1] [,2]
[1,] 3 -1
[2,] -4 -5
b
[,1] [,2]
[1,] 0 5
[2,] -1 7
* b a
[,1] [,2]
[1,] 0 -5
[2,] 4 -35
6.2 The tensor product
\[ \begin{eqnarray*} & & v = \left(\begin{matrix} -2 \\ 3 \\ 0 \\ 1 \end{matrix} \right), \quad w = \left(\begin{matrix} 1 & 4 & -7 & 9 \end{matrix} \right), \\[5pt] & & v \otimes w = \left(\begin{matrix} -2 & -8 & 14 & -18 \\ 3 & 12 & -21 & 27 \\ 0 & 0 & 0 & 0 \\ 1 & 4 & -7 & 9 \end{matrix} \right). \end{eqnarray*} \]
v
[-2, 3, 0, 1]
w
[1, 4, -7, 9]
v \(\otimes\) w =
= len(v)
nrow = len(w)
ncol
= []
result for i in range(nrow):
= []
row for j in range(ncol):
*w[j])
row.append(v[i]
result.append(row) result
[[-2, -8, 14, -18], [3, 12, -21, 27], [0, 0, 0, 0], [1, 4, -7, 9]]
We use the numpy.tensordot()
function. The axis
determine the type of tensors to be are calculated. We use axis=0
to get the tensor product.
V
array([-2, 3, 0, 1])
W
array([ 1, 4, -7, 9])
0) numpy.tensordot(V, W,
array([[ -2, -8, 14, -18],
[ 3, 12, -21, 27],
[ 0, 0, 0, 0],
[ 1, 4, -7, 9]])
It is possible to use outer()
function or the tensor operator %o%
from the tensor
package.
v
[1] -2 3 0 1
w
[1] 1 4 -7 9
outer(v, w)
[,1] [,2] [,3] [,4]
[1,] -2 -8 14 -18
[2,] 3 12 -21 27
[3,] 0 0 0 0
[4,] 1 4 -7 9
library("tensor")
%o% w v
[,1] [,2] [,3] [,4]
[1,] -2 -8 14 -18
[2,] 3 12 -21 27
[3,] 0 0 0 0
[4,] 1 4 -7 9