Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Linear Algebra Review and SciPy Basics

Definitions and notation in this review follow Section 2.2 Vectors and Matrices in Biegler (2010), with the invertibility discussion cross-checked against Lay, Linear Algebra and Its Applications, 3rd ed.

Recommended Links

Tip: We will mostly use SciPy for linear algebra. (It has more sophisticated capabilities than NumPy.) We will use NumPy if/when SciPy does not offer a specific command.

# Load required Python libraries.
import numpy as np
from scipy import linalg

# Seed the random number generator so this notebook is reproducible
# (Pyomo style guide, section 8).
np.random.seed(0)

Notation used in the references

  • Scalar constants and variables - Greek letters

  • Vectors - Lower case Roman letters

  • Matrices - Upper case Roman letters

The notation for lectures will (hopefully) be obvious from context. I sometimes use Greek letters for constants and Roman letters for variables... unless the Greek letters have engineering or scientific meaning.

Matrix Operations

For ARn×mA\in\mathbb{R}^{n\times m} and BRm×pB\in\mathbb{R}^{m\times p}, the product C=ABRn×pC=AB\in\mathbb{R}^{n\times p} has entries

Cij=k=1mAikBkj.C_{ij}=\sum_{k=1}^{m}A_{ik}B_{kj}.

The inner dimensions must match. The transpose interchanges rows and columns, (AT)ij=Aji(A^\mathsf{T})_{ij}=A_{ji}. A square matrix is symmetric when A=ATA=A^\mathsf{T}, diagonal when Aij=0A_{ij}=0 for iji\ne j, and the identity has Iij=1I_{ij}=1 for i=ji=j and zero otherwise.

# Generate random matrices
n = 2
m = 3

Ao = np.random.rand(n, m)
Bo = np.random.rand(m, n)

print("Ao = ")
print(Ao)
print("\nBo = ")
print(Bo)


# What is the dimension of A*B? Try to answer this without the computer.

# Calculate A*B using Python
print("\nAo*Bo =")
print(Ao.dot(Bo))
Ao = 
[[0.5488135  0.71518937 0.60276338]
 [0.54488318 0.4236548  0.64589411]]

Bo = 
[[0.43758721 0.891773  ]
 [0.96366276 0.38344152]
 [0.79172504 0.52889492]]

Ao*Bo =
[[1.40657799 1.08244885]
 [1.15806481 0.98996907]]

Activity

  • Calculate AoTA_o^{T}

  • Create a square matrix and check if it is symmetric

  • Create a 3x3 identity matrix

# Transpose
print("transpose(Ao) = \n", Ao.transpose(), "\n")

# Create a square matrix. Is it symmetric?

# Identify matrix
print("I = \n", np.identity(3), "\n")
transpose(Ao) = 
 [[0.5488135  0.54488318]
 [0.71518937 0.4236548 ]
 [0.60276338 0.64589411]] 

I = 
 [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]] 

Determinant

For a square matrix ARn×nA\in\mathbb{R}^{n\times n}, expansion along any row ii gives

det(A)=j=1n(1)i+jAijdet(Aij),\det(A)=\sum_{j=1}^{n}(-1)^{i+j}A_{ij}\det(A_{ij}),

where AijA_{ij} is the submatrix formed by deleting row ii and column jj. Useful properties are

det(AB)=det(A)det(B),det(AT)=det(A),det(αA)=αndet(A),det(I)=1.\det(AB)=\det(A)\det(B),\qquad \det(A^\mathsf{T})=\det(A),\qquad \det(\alpha A)=\alpha^n\det(A),\qquad \det(I)=1.

The exponent nn in the scaling law matters: multiplying the whole matrix by α\alpha scales each of its nn rows. A square matrix is singular exactly when det(A)=0\det(A)=0.

# Generate random matrices
nd = 2

Ad = np.random.rand(nd, nd)
Bd = np.random.rand(nd, nd)

Cd = np.array([(1, 0, 0), (-2, 0, 0), (0, 1, 1)])

print("Ad = ")
print(Ad)
print("\nBd = ")
print(Bd)
print("\nCd = ")
print(Cd)
Ad = 
[[0.56804456 0.92559664]
 [0.07103606 0.0871293 ]]

Bd = 
[[0.0202184  0.83261985]
 [0.77815675 0.87001215]]

Cd = 
[[ 1  0  0]
 [-2  0  0]
 [ 0  1  1]]

Activity

  • Verify the properties listed under Some properties for the determinant include

  • Are AdA_d, BdB_d or CdC_d singular?

# Property 1
print("det(A*B) = ", linalg.det(Ad.dot(Bd)))

print("det(A)*det(B) = ", linalg.det(Ad) * linalg.det(Bd))
det(A*B) =  0.010247347495272154
det(A)*det(B) =  0.010247347495272162
# Property 2
# Property 3
# Property 4

Another Example (for at home)

Expand along the first row of

A=[150241020].A=\begin{bmatrix}1&5&0\\2&4&-1\\0&-2&0\end{bmatrix}.

Then

det(A)=1det[4120]5det[2100]+0det[2402]=2.\det(A)=1\det\begin{bmatrix}4&-1\\-2&0\end{bmatrix}-5\det\begin{bmatrix}2&-1\\0&0\end{bmatrix}+0\det\begin{bmatrix}2&4\\0&-2\end{bmatrix}=-2.

This is the worked example previously shown as a crop from Lay; it is written natively here so the signs and arithmetic remain searchable and accessible.

Rank

The rank of ARn×mA\in\mathbb{R}^{n\times m} is the dimension of its column space (equivalently, its row space). It is also the size of the largest square submatrix with nonzero determinant. Full rank means rank(A)=min(n,m)\operatorname{rank}(A)=\min(n,m); otherwise AA is rank deficient.

Activity

  • Predict the rank of AoTA_o^T, BoTB_o^T, AdA_d, BdB_d, CdC_d

  • Calculate np.linalg.matrix_rank(). Were you correct?

print("rank(A_o) = ", np.linalg.matrix_rank(Ao), "\n")

# Fill in remainder here.
rank(A_o) =  2 

Inverse

A square matrix AA is invertible when there is a matrix A1A^{-1} satisfying AA1=A1A=IAA^{-1}=A^{-1}A=I. Then Ax=bAx=b has the unique solution x=A1bx=A^{-1}b, although numerical software should solve the system directly rather than form the inverse. Also, det(A1)=1/det(A)\det(A^{-1})=1/\det(A). An orthogonal matrix QQ satisfies Q1=QTQ^{-1}=Q^\mathsf{T}.

Activity

  1. Calculate the inverse of the square matrix AdA_d and verify that Ad1Ad=IA_d^{-1} A_d = I. (The rectangular matrix AoA_o has no two-sided inverse.)

  2. Verify that det(Ad1)=1/det(Ad)\det(A_d^{-1}) = 1/\det(A_d)

  3. Is QQ an orthogonal matrix?

# Task 1

# Task 2

# Task 3
# Is this an orthogonal matrix? Yes, no, or need more information?
theta = 0.2
Q = np.array([(np.cos(theta), -np.sin(theta)), (np.sin(theta), np.cos(theta))])

print("Q = ")
print(Q)
Q = 
[[ 0.98006658 -0.19866933]
 [ 0.19866933  0.98006658]]

Solving Linear Systems

Consider the linear system Alx=blA_l x = b_l

Al = np.array([(4, 3), (6, 3)])
bl = np.array([1, 0])
print("Al = \n", Al)
print("\nbl = \n", bl)
Al = 
 [[4 3]
 [6 3]]

bl = 
 [1 0]

Explicit Inverse

Calculate xx by explicitly using Al1A_l^{-1}. Hint: Use linalg.inv().

Ainv = linalg.inv(Al)
print("Ainv = \n", Ainv, "\n")

# Now calculate and print x
Ainv = 
 [[-0.5         0.5       ]
 [ 1.         -0.66666667]] 

LU Decomposition

Perform LU decomposition on AlA_l. What structures do the PP, LL, and UU matrices have?

# Create test linear systems
# The transpose is deliberate: it gives a permutation matrix P that is NOT
# symmetric, so the MATLAB and SciPy conventions below give different answers.
A = np.random.rand(4, 4).T
print("A = \n", A, "\n")

b = np.random.rand(4, 1)
print("b = \n", b, "\n")
A = 
 [[0.97861834 0.11827443 0.52184832 0.45615033]
 [0.79915856 0.63992102 0.41466194 0.56843395]
 [0.46147936 0.14335329 0.26455561 0.0187898 ]
 [0.78052918 0.94466892 0.77423369 0.6176355 ]] 

b = 
 [[0.61209572]
 [0.616934  ]
 [0.94374808]
 [0.6818203 ]] 

# Perform LU decomposition
P, L, U = linalg.lu(A)

# Permutation matrix
print("P = \n", P)

# Lower triangular matrix
print("L = \n", L)

# Upper triangular matrix
print("U = \n", U)

# Verify result
print("P*L*U = \n", P.dot(L.dot(U)), "\n")
P = 
 [[1. 0. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]
 [0. 1. 0. 0.]]
L = 
 [[1.         0.         0.         0.        ]
 [0.79758282 1.         0.         0.        ]
 [0.81661924 0.63896662 1.         0.        ]
 [0.47156214 0.10299414 0.07659477 1.        ]]
U = 
 [[ 0.97861834  0.11827443  0.52184832  0.45615033]
 [ 0.          0.85033527  0.35801643  0.25381783]
 [ 0.          0.         -0.24024999  0.03375169]
 [ 0.          0.          0.         -0.22504038]]
P*L*U = 
 [[0.97861834 0.11827443 0.52184832 0.45615033]
 [0.79915856 0.63992102 0.41466194 0.56843395]
 [0.46147936 0.14335329 0.26455561 0.0187898 ]
 [0.78052918 0.94466892 0.77423369 0.6176355 ]] 

Is P orthogonal?

print("inv(P) = \n", linalg.inv(P), "\n")

print("transpose(P) = \n", P.T, "\n")

print("P.T*P = \n", np.matmul(P.T, P), "\n")
inv(P) = 
 [[ 1.  0. -0.  0.]
 [ 0.  0. -0.  1.]
 [ 0.  1. -0.  0.]
 [ 0.  0.  1.  0.]] 

transpose(P) = 
 [[1. 0. 0. 0.]
 [0. 0. 0. 1.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]] 

P.T*P = 
 [[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]] 

Yes! We see that PT=P1P^T = P^{-1} and PTP=IP^T \cdot P = I.

MATLAB

Defines LU decomposition as follows:

PA=LUP \cdot A = L \cdot U

Consider

Ax=bA \cdot x=b

Using MATLAB LU definition

PA=LUP\cdot A = L\cdot U
PTPA=PTLUP^T \cdot P \cdot A = P^T \cdot L \cdot U
A=PTLUA = P^T \cdot L \cdot U

Substitute into linear system:

PTLUx=bP^T \cdot L \cdot U \cdot x = b
LUx=PbL \cdot U \cdot x = P \cdot b

Let y=Uxy = U \cdot x and substitute.

Step 1. Solve Ly=PbL \cdot y = P \cdot b for yy

Step 2. Solve Ux=yU \cdot x = y for xx

Pb = P.dot(b)
print("P*b = ", Pb, "\n")

yLU = linalg.solve(L, Pb)
print("yLU = ", yLU, "\n")

xLU = linalg.solve(U, yLU)
print("xLU = ", xLU, "\n")

# Check: does this x actually solve A*x = b?
print("||A*x - b|| = ", np.linalg.norm(A.dot(xLU) - b), "\n")
P*b =  [[0.61209572]
 [0.94374808]
 [0.6818203 ]
 [0.616934  ]] 

yLU =  [[ 0.61209572]
 [ 0.45555105]
 [-0.10911075]
 [ 0.28973105]] 

xLU =  [[ 0.98256068]
 [ 0.80496658]
 [ 0.27328499]
 [-1.28746252]] 

||A*x - b|| =  0.42382052546128374 

SciPy

Defines LU decomposition as:

A=PLUA = P \cdot L \cdot U

Consider

Ax=bA \cdot x=b

SciPy LU definition

A=PLUA = P \cdot L\cdot U

Substitute into linear system:

PLUx=bP \cdot L \cdot U \cdot x = b
PTPLUx=PTbP^T \cdot P \cdot L \cdot U \cdot x = P^T \cdot b
LUx=PTbL \cdot U \cdot x = P^T \cdot b

Let y=Uxy = U \cdot x and substitute.

Step 1. Solve Ly=PTbL \cdot y = P^T \cdot b for yy

Step 2. Solve Ux=yU \cdot x = y for xx

## LU decomposition algorithm.
Pb = (P.T).dot(b)
print("P.T*b = ", Pb, "\n")

yLU = linalg.solve(L, Pb)
print("yLU = ", yLU, "\n")

xLU = linalg.solve(U, yLU)
print("xLU = ", xLU, "\n")

# Check: does this x actually solve A*x = b?
print("||A*x - b|| = ", np.linalg.norm(A.dot(xLU) - b), "\n")
P.T*b =  [[0.61209572]
 [0.6818203 ]
 [0.616934  ]
 [0.94374808]] 

yLU =  [[ 0.61209572]
 [ 0.19362327]
 [-0.00663395]
 [ 0.63567297]] 

xLU =  [[ 1.99078818]
 [ 1.22630486]
 [-0.3692182 ]
 [-2.82470629]] 

||A*x - b|| =  3.6821932062951477e-16 

What is the difference? A transpose.

Verify our answer with linalg.solve

Solve the linear system using linalg.solve

x = linalg.solve(Al, bl)
print("x = \n", x)
x = 
 [-0.5  1. ]

Invertible Matrix Theorem

For a square n×nn\times n matrix AA, the following statements are equivalent:

  • AA is invertible; its row-reduced form is II; and it has nn pivots.

  • Ax=0Ax=0 has only the trivial solution, so Nul(A)={0}\operatorname{Nul}(A)=\{0\} and dimNul(A)=0\dim\operatorname{Nul}(A)=0.

  • The columns of AA are linearly independent, form a basis of Rn\mathbb{R}^n, and span Rn\mathbb{R}^n; equivalently rank(A)=n\operatorname{rank}(A)=n.

  • The map xAxx\mapsto Ax is both one-to-one and onto; therefore Ax=bAx=b has a solution for every bRnb\in\mathbb{R}^n.

  • A left or right inverse exists; ATA^\mathsf{T} is invertible; det(A)0\det(A)\ne0; and zero is not an eigenvalue.

  • The row space is Rn\mathbb{R}^n, (ColA)={0}(\operatorname{Col}A)^\perp=\{0\}, (NulA)=Rn(\operatorname{Nul}A)^\perp=\mathbb{R}^n, and AA has nn nonzero singular values.

These equivalences consolidate Lay’s Invertible Matrix Theorem into one searchable list.

Eigenvectors and Eigenvalues

For square AA, a nonzero vector vv is an eigenvector with eigenvalue λ\lambda when Av=λvAv=\lambda v. Thus AλIA-\lambda I is singular and eigenvalues are roots of det(AλI)=0\det(A-\lambda I)=0. Counting algebraic multiplicity, det(A)=iλi\det(A)=\prod_i\lambda_i.

If AA is real and symmetric, the spectral theorem gives A=VΛVTA=V\Lambda V^\mathsf{T} with real eigenvalues and orthonormal eigenvectors. Only under this symmetry assumption can definiteness be read directly from eigenvalue signs: all positive means positive definite, all nonnegative means positive semidefinite, and mixed signs mean indefinite.

A nonsymmetric real matrix may have complex eigenvalues, and its eigenvalues do not in general classify the quadratic form xTAxx^\mathsf{T}Ax. That form is governed by the symmetric part (A+AT)/2(A+A^\mathsf{T})/2. For rectangular matrices, singular values σi0\sigma_i\ge0—the square roots of the eigenvalues of ATAA^\mathsf{T}A—play the analogous role.

Level sets of a quadratic form, as one picture

Level sets of the quadratic form q(x)=\tfrac12 x^\mathsf{T} A x for symmetric positive definite A, with the eigenvectors drawn as the principal axes. Both panels use the same eigenvectors and the same level q=2; only the eigenvalues change.

Eigenvalues and eigenvectors of a symmetric matrix have a picture: they are the principal axes of the level sets of q(x)=12xTAxq(x) = \tfrac12 x^\mathsf{T} A x. In the rotated coordinates z=VTxz = V^\mathsf{T} x the level set is λ1z12+λ2z22=2q\lambda_1 z_1^2 + \lambda_2 z_2^2 = 2q, so the semi-axis along viv_i has length 2q/λi1/λi\sqrt{2q/\lambda_i} \propto 1/\sqrt{\lambda_i} --- a large eigenvalue means steep, hence short.

The right-hand panel is the same picture with a condition number of 100. Note the axis ratio is κ(A)\sqrt{\kappa(A)}, not κ(A)\kappa(A); by the time you reach the κ1014\kappa \approx 10^{14} you will meet in the Condition Number section below, the ellipse is not drawable.

This figure is rendered from figures/plots/quadratic-form-level-sets.py and is the same image printed in the course pack, so what you annotate in lecture and what you see here cannot drift apart. The cells below are the live version.

Ae = np.array([(0, -4, -6), (-1, 0, -3), (1, 2, 5)])
print("Ae = \n", Ae)
Ae = 
 [[ 0 -4 -6]
 [-1  0 -3]
 [ 1  2  5]]

Activity

  • Do you expect this matrix to have negative eigenvalues? Choose: Yes / No / Cannot tell from inspection.

  • Calculate the eigenvalues.

  • The eigenvalues of this nonsymmetric matrix are positive. Does that prove xTAex>0x^\mathsf{T}A_e x>0 for every nonzero xx?

  • Form the symmetric part H=(Ae+AeT)/2H=(A_e+A_e^\mathsf{T})/2 and use its eigenvalues to classify the quadratic form.

  • Calculate det(Ae)\det(A_e) using only the eigenvalues of AeA_e.

# Expect negative eigenvalues?
# Yes / No / Cannot tell from inspection?
# Write a sentence to justify your answer.

### Calculate eigenvalues

# Matrix Ae is nonsymmetric
print("Matrix = \n", Ae, "\n")
l, v = linalg.eig(Ae)
print("Eigenvalues = ", l, "\n")
print("Eigenvectors = \n", v, "\n")

# The symmetric part, not Ae's eigenvalues, classifies x.T @ Ae @ x
H = 0.5 * (Ae + Ae.T)
print("Eigenvalues of symmetric part = ", linalg.eigvalsh(H))
Matrix = 
 [[4 3]
 [6 3]] 

Eigenvalues =  [ 7.77200187+0.j -0.77200187+0.j] 

Eigenvectors = 
 [[ 0.62246561 -0.53222953]
 [ 0.78264715  0.8466001 ]] 

Singular Value Decomposition

Notes will be given in class.

Activity

  • Calculate singular value decomposition of AlA_l and CdC_d

  • What is the rank of each matrix?

# Matrix Al
print("Matrix = \n", Al, "\n")
U, s, Vh = linalg.svd(Al)
print("U = \n", U, "\n")
print("S = \n", s, "\n")
print("Vh = \n", Vh, "\n")

## Rank (from inspecting singular values)?
Matrix = 
 [[4 3]
 [6 3]] 

U = 
 [[-0.59581566 -0.80312122]
 [-0.80312122  0.59581566]] 

S = 
 [8.33557912 0.71980602] 

Vh = 
 [[-0.86400595 -0.50348159]
 [ 0.50348159 -0.86400595]] 

Vector and Matrix Norms

A vector norm x||x|| measures the length of a vector. It maps Rn\mathbb{R}^n into the nonnegative reals and satisfies three properties:

x=0x=0,αx=αx,x+yx+y||x|| = 0 \Rightarrow x = 0, \qquad ||\alpha x|| = |\alpha| \cdot ||x||, \qquad ||x + y|| \leq ||x|| + ||y||

The last one is the triangle inequality. The pp-norm is xp=(i=1nxip)1/p||x||_p = \left( \sum_{i=1}^n |x_i|^p \right)^{1/p} for p1p \geq 1, and three cases matter here:

  • x1=i=1nxi||x||_1 = \sum_{i=1}^n |x_i| --- the 1-norm

  • x2=(i=1nxi2)1/2=xTx||x||_2 = \left( \sum_{i=1}^n x_i^2 \right)^{1/2} = \sqrt{x^T x} --- the Euclidean norm

  • x=maxixi||x||_{\infty} = \max_i |x_i| --- the max norm

Also xTyxpyq|x^T y| \leq ||x||_p \cdot ||y||_q whenever 1/p+1/q=11/p + 1/q = 1; the case p=q=2p = q = 2 is the Cauchy-Schwarz inequality.

Matrix norms are induced by vector norms:

A=maxx0AxxAxAx||A|| = \max_{x \neq 0} \frac{||Ax||}{||x||} \qquad \Rightarrow \qquad ||Ax|| \leq ||A|| \cdot ||x||

The rules for the three induced norms are

  • A1=maxjiAij||A||_1 = \max_j \sum_i |A_{ij}| --- largest column sum

  • A=maxijAij||A||_{\infty} = \max_i \sum_j |A_{ij}| --- largest row sum

  • A2=||A||_2 = the largest singular value of AA

Finally, the induced norms and the Frobenius norm AF=(ijAij2)1/2||A||_F = \left( \sum_i \sum_j A_{ij}^2 \right)^{1/2} all satisfy

ABAB||AB|| \leq ||A|| \cdot ||B||

for any two compatible matrices AA and BB. This is submultiplicativity, and it is what the condition number in the next section rests on.

Definition 2.7, pp. 21-22 in Biegler (2010).

x = np.random.rand(3, 1)
print("x = \n", x)

y = np.random.rand(3, 1)
print("y = \n", y)
x = 
 [[0.3595079 ]
 [0.43703195]
 [0.6976312 ]]
y = 
 [[0.06022547]
 [0.66676672]
 [0.67063787]]

Activity

  • Verify the vector norm properties hold for the p-2 norm using xx and yy. Hint: linalg.norm()

  • Calculate Al1||A_l||_1, Al||A_l||_{\infty} and Al2||A_l||_2 using both the rules given above and SciPy

Condition Number

Activity

  • Estimate the condition number of AlA_l by inspecting the SVD results.

  • Calculate the condition number of AlA_l using np.linalg.cond()