Vectors & Matrices (NumPy)
Let's begin by importing the NumPy package.
NumPy includes some tools for working with linear algebra in the numpy.linalg
module. However, unless you really don’t want to add SciPy as a dependency to your project, it’s typically better to use scipy.linalg
for the following reason:
- As explained in the official documentation,
scipy.linalg
contains all the functions innumpy.linalg
plus some extra advanced functions not included innumpy.linalg
.
Therefore, when we need to use linear algebra specific functions we'll load the scipy.linalg
library. The first instance where we do this below is when we compute matrix inverses.
NumPy Arrays
Vectors and Matrices are created as instances of a numpy array. We can think of a 1D NumPy array as a list of numbers (or row-vector), and a 2D number array as a matrix.
1D Arrays
Let's create a 1D array of integers.
We can verify the dimension, shape and size.
ndim
returns the dimension of the array. This is how many indices are needed to specify an entry.shape
returns a tuple ofndim
numbers. For a 2D array this is (rows, columns).size
returns the total number of entries in the array.
Therefore, since ndim
is 1 this is a 1-dimensional array.
The shape
method returns a python tuple of length 1 - this tells us the array is 1D and has 4 entries. The size
method also tells us the array has 4 entries.
2D Arrays
Let's create a 2D array.
The array was entered as a list of rows.
We can verify the dimension of the array. The following tells us it is 2-dimensional, therefore to select an entry in the array we need 2 indices to locate it (row number and column number). We can also verify the shape and size too.
ndim
returns the dimension of the array. This is how many indices are needed to specify an entry.shape
returns a tuple ofndim
numbers. For a 2D array this is (rows, columns).size
returns the total number of entries in the array.
.reshape
Another way to create a 2D array is to first create a 1D array and then use the .reshape
method to change its shape as desired.
- This does not change the array stored in the variable A, it creates a new instance that has been reshaped.
.mat
NumPy has a built in matrix object, which is really just a 2D array. The only difference in using .mat
as opposed to .array
is that the operator *
can be used for the usual matrix product. More on this below.
We can create a matrix directly as follows.
We can also take an existing 2D array and convert it to a matrix with asmatrix
.
There isn't much of an advantage using .mat
over .array
for 2D arrays so we'll always use an array. Just so you are aware of some shortcuts available when working with .mat
objects, we have .T
for transpose, .H
for Hermetian, and .I
for inverse. We also have *
for matrix multiplication. However, we have similar methods for .array
objects which we will cover below.
Indexing
In Python indices begin at 0. Therefore, to select the first entry of array a
we use a[0]
.
We can also use index slicing to select a subarray.
- select entries from index 1 (inclusive) to 3 (non-inclusive). That is, select entries with index from 1 to 2.
This can be done for 2D arrays by specifying both indices.
- This selects the entry in row indexed by 3, and column indexed by 2. Remember indexing starts at 0.
We can use slicing to select submatricies.
- This selects all entries from row 0 (inclusive) up to row 2 (not inclusive), and column 0 (inclusive) up to column 3 (not inclusive). This will be a 2x3 submatrix.
An entire column can be selected using slicing.
:
means run over all values of the index. Therefore, this selects the first column.
Notice that b
looks to be a 1D array (rather than column vector, which is a 2D array of shape 4x1). We can verify this by checking the dimension.
Unfortunately, even though we selected a column of the array this returned a 1D array, which is essentially a row vector. Given the circumstance we may have wanted this to be a column vector. We can reshape it into a 4x1 column vector.
- We change the shape of
b
and then rewrite the new data to the same variable name.
Now we have successfully extracted the first column as a column vector. We could do this in one line as follows:
Special Matrices
NumPy has some predefined special matrices which can be useful for quickly constructing matrices.
Identity
An \(n \times n\) identity matrix is constructed with eye(n)
.
Zeros
An nxm zero matrix is constructed with zeros([n,m])
.
Ones
An nxm matrix of all 1's is constructed with ones([n,m])
.
Example
Construct the \(5 \times 5\) lower diagonal matrix.
Example
Construct the \(5 \times 5\) matrix with lower and upper diagonals of \(1\)'s.
diagonal and block
To create a diagonal matrix use the NumPy diag
function.
To create a block matrix use the NumPy block
function.
B2 = np.array([[2,2],[2,2]])
B3 = np.array([[3,3,3],[3,3,3],[3,3,3]])
np.block([[B2,np.zeros([2,3])],
[np.zeros([3,2]),B3]])
array([[2., 2., 0., 0., 0.],
[2., 2., 0., 0., 0.],
[0., 0., 3., 3., 3.],
[0., 0., 3., 3., 3.],
[0., 0., 3., 3., 3.]])
random
Use np.random.rand
to create an array of the given shape and populate it with random samples from a uniform distribution over [0, 1).
[[0.40104298 0.25146289 0.94189368 0.320897 ]
[0.20052053 0.25960576 0.18908785 0.07176892]
[0.29962823 0.31623458 0.40490173 0.99967804]]
To generate a matrix with random integer values use numpy.random.randint
. The first two arguments are the lower and upper bounds on the entries.
Vector Operations
Dot Product
The dot product of two vectors (constructed as 1D arrays) can be done with the numpy.dot
function. We also give two alternate methods as well.
Length (Norm)
The length (also called the norm) of an \(n\)-dimensional vector \(\vec{v} = [v_1, v_2, \ldots , v_n]\) is defined by
This function isn't built directly into NumPy, but numpy.dot
and numpy.sqrt
are so we can use those.
If you plan on computing the length frequently, you may consider just defining a function.
This is how I would do it if I wasn't already importing the SciPy package. However, if we are using the SciPy package already, then we could call scipy.linalg.norm
to compute the length of a vector.
Angle between two vectors
The angle \(\theta\) between two vectors satisfies
a = np.array([1,0])
b = np.array([1,1])
np.arccos( (np.dot(a,b)) / (np.sqrt(np.dot(a,a)*np.dot(b,b)) ) )
Notice, this is just the decimal value of \(\pi/4\).
We can build this into a function to make it easier to call.
This value is \(3\pi/4\).
Matrix Operations
Matrix Scalar Multiplication
Matrix Addition
Matrix Multiplication
Let's start with the two matrices.
\(A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}\) and \(B = \begin{bmatrix} -2 & 5 \\ 7 & 1 \end{bmatrix}\)
The matix multiplication operator is @
.
Alternatively, we could use numpy.matmul
.
Here is another example,
\(C = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}\) and \(D = \begin{bmatrix} 1 & 2 & 1 & 2 \\ 1 & 2 & 1 & 2 \\ 1 & 2 & 1 & 2 \end{bmatrix}\)
The matrix product is a 2x4 matrix.
Example
Putting this all together, let's compute \(3I + 2A -AB\) for
\(A = \begin{bmatrix} 1 & 2 \\ 2 & 1 \end{bmatrix}\) and \(B = \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}\)
Info
One may think to use *
as a multiplication operator, but for NumPy arrays it multiplies entries elementwise.
This will do elementwise multiplication on any two matrices as long as they have the same shape.
For .mat
objects *
will do the usual matrix multiplication. This used to be one major advantage for using .mat
over .array
, but with the @
operator for matrix multiplication on 2D arrays, .mat
looses this advantage over .array
.
Matrix Powers
To compute a matrix power we use the matrix_power
function in the numpy.linalg
library.
It is customary to import this in with the name mpow
.
Transpose
The transpose of a matrix can be taken by using either of these methods on an array
: .T
or .transpose
.
This can equivalently be done as a function call.
Inverse
To find the inverse of a matrix this is where we need to bring in the scipy
library.
In particular we need scipy.linalg.inv
. To do this we'll just bring in the entire scipy.linalg
library as la
.
Trace
We can find the trace of a matrix using the .trace
function on an array.
This can also be done by calling the function numpy.trace
.
Determinant
We can find the determinant using the scipy.linalg.det
function.
Rank
We can find the rank using the numpy.linalg.matrix_rank
function.
Examples
Characteristic Polynomial and the Cayley-Hamilton Theorem
The characteristic polynomial of a 2x2 matrix \(A\) is
The Cayley-Hamilton Theorem states that any square matrix satisfies its characteristic polynomial. This means
Let's check that this is the case for some matrices.
def check_cayley_hamilton(A):
# input: a 2x2 matrix A
trace_A = np.trace(A)
det_A = la.det(A)
I = np.eye(2)
return A@A - trace_A*A + det_A*I
Example
Projections
The projection of a vector \(\vec{v}\) onto a vector \(\vec{w}\) is
The perpendicular of the projection of \(\vec{v}\) onto \(\vec{w}\) is
This decomposes the vector \(\vec{w}\) into two parts:
where \(\operatorname{proj}_{\vec{w}}(\vec{v})\) is parallel to \(\vec{w}\) and \(\operatorname{perp}_{\vec{w}}(\vec{v})\) is perpenicular to \(\vec{w}\).
Let's define a function called proj
which returns the projection of \(\vec{v}\) onto \(\vec{w}\), and a function called perp
.
def proj(v,w):
# input: v, w are vectors represented as 1D arrays
# output: projection of v onto w
return ((np.dot(v,w)/np.dot(w,w))*w)
def perp(v,w):
# input: v, w are vectors represented as lists
# output: perpendicular of the projection of v onto w
return v-proj(v,w)
Example
Consider the vectors \(\vec{v} = [4,2]\) and \(\vec{w} = [1,1]\). The projection of \(\vec{v}\) onto \(\vec{w}\) is \(3\vec{w} = [3,3]\).
The perpendicular is \([1,-1]\):
We can verify the sum is the vector \(\vec{v}\).
Example
Consider the vectors \(\vec{v} = [1,3,-1]\) and \(\vec{w} = [1,1,2]\). The projection of \(\vec{v}\) onto \(\vec{w}\) is \(3\vec{w} = [1/3,1/3,2/3]\).
The perpendicular is \([2/3, 8/3, -5/3]\):
We can check these sum to \(\vec{v}\):