import numpy as np
Matrix norms, unitary matrices
Useful tools using numpy
Commented - equal jax operators for advanced users
= 3, 3 n, m
# random matrix
=(n,m)) np.random.normal(size
# Identity matrix
np.identity(n)
# defining your own matrix
4, 8, 15],
np.array([[16, 23, 42],
[0, 0, 0]]) [
# matrix multiplication
= np.random.normal(size=(n,m))
a = np.random.normal(size=(n,m))
b = np.random.normal(size=(n,))
x print(np.matmul(a, b))
print()
print(np.matmul(a, x))
= np.random.normal(size=(n,m))
a for i,j in enumerate(a):
print(i, j[0])
0] a[:,
Matrix Norms Calculation
Important case of operator norms are matrix p-norms, which are defined for \|\cdot\|_* = \|\cdot\|_{**} = \|\cdot\|_p
Among all p-norms three norms are the most common ones:
p = 1, \quad \Vert A \Vert_{1} = \displaystyle{\max_j \sum_{i=1}^n} |a_{ij}|
p = 2, \quad spectral norm, denoted by \Vert A \Vert_2
p = \infty, \quad \Vert A \Vert_{\infty} = \displaystyle{\max_i \sum_{j=1}^m} |a_{ij}|
Let us work with = 1 and inf now.
enumerate(a)
Lets write our own functions to calculate 0 and inf norms:
def p_1_norm(a):
= a.shape
n,m max = 0
for i in range(m):
max = np.max([np.sum(np.abs(a[:,[i]])), max])
return max
def p_inf_norm(a):
= a.shape
n,m max = 0
for i in range(n):
max = np.max([np.sum(np.abs(a[i])), max])
return max
= 3, 3
m, n = np.random.normal(size=(n,m)) #Random n x m matrix
a print(a)
print("p_1 norm: ", p_1_norm(a))
print("p_inf norm: ", p_inf_norm(a))
Now, make up a toy example matrix for which these two are not equal:
= np.array([[1, 0, 0],
b 0, 1, 0],
[0, 0, 1]])
[
print("p_1 norm: ", p_1_norm(b))
print("p_inf norm: ", p_inf_norm(b))
print(p_1_norm(b) == p_inf_norm(b))
Creative task: invent your own matrix norm and check that it satisfies three norm requirements
\Vert \cdot \Vert is called a matrix norm if it is a vector norm on the vector space of n \times m matrices and:
\|A\| \geq 0 and if \|A\| = 0 then A = O
\|\alpha A\| = |\alpha| \|A\|
\|A+B\| \leq \|A\| + \|B\| (triangle inequality)
Additionally some norms can satisfy the submultiplicative property: \Vert A B \Vert \leq \Vert A \Vert \Vert B \Vert
def super_norm(a):
= a.shape
n,m sum = 0
for i in range(n):
sum += np.abs(a[i,i])
return sum
= 50, 50 n, m
Now, lets check all requirements. Attention! If some conditions seem to not work, recall previous lecture to understand and fix the problem
= True
is_nonnegative
for i in range(42):
= np.random.normal(size=(n,m))
a
if super_norm(a) < 0:
= False
is_nonnegative
print("is_nonnegative: ", is_nonnegative)
= True
respects_zero
= np.zeros((n,m))
a
if super_norm(a) != 0:
= False
respects_zero
print("respects_zero: ", respects_zero)
= True
respects_scalar_mult
for i in range(42):
= np.random.normal(size=(n,m))
a = np.abs(np.random.normal())
alpha
if np.abs(super_norm(alpha * a) - alpha * super_norm(a)) > 0.001:
= False
respects_scalar_mult
print("respects_scalar_mult: ", respects_scalar_mult)
= True
triangle_inequality
for i in range(42):
= np.random.normal(size=(n,m))
a = np.random.normal(size=(n,m))
b
if super_norm(a + b) > super_norm(a) + super_norm(b):
= False
triangle_inequality
print("triangle_inequality: ", triangle_inequality)
# Not required to be a norm
= True
is_submultiplicative
for i in range(42):
= np.random.normal(size=(n,m))
a = np.random.normal(size=(n,m))
b
if super_norm(np.matmul(a, b)) > super_norm(a) * super_norm(b):
= False
is_submultiplicative
print("is_submultiplicative: ", is_submultiplicative)
Householder Martices
Householder matrix is the matrix of the form: H \equiv H(v) = I - 2 vv^*, where v is an n \times 1 column and v^* v = 1.
It is also a reflection: Hx = x - 2(v^* x) v
Attention! If it does not work, remember about vector norm
Build your own from a vector
def build_householder(v):
# v - vector of size n
= v/np.linalg.norm(v)
v = np.identity(v.size)
a
= a - 2*np.outer(v, v)
H
return H
= np.random.normal(size=(3))
v print(v)
= build_householder(v)
h print(h)
See how it reflects vectors
= np.random.normal(size=(3))
v = build_householder(v)
h
= np.random.normal(size=(3))
x print(x)
print(np.matmul(h, x))
= np.array([0, 1, -1])
v = build_householder(v)
h = np.array([0, 1, 0])
x print(np.round(h , decimals=2))
print(x)
print(np.round(h @ x.T, decimals=2))
Optional: check that it indeed is also a reflection
Hx = x - 2(v^* x) v
= np.random.normal(size=(3))
v = v/np.linalg.norm(v)
v = build_householder(v)
h
= np.random.normal(size=(3))
x
= np.array([0, 1, -1])
v = v/np.linalg.norm(v)
v = build_householder(v)
h = np.array([0, 1, 0])
x
= h @ x.T
hx
= x - 2 * np.dot(v.T,x) * v
reflected
print("initial vector: ", x)
print("transofrmed by matrix: ", hx)
print("reflected by vector: ", reflected)
Check unitarity
Use numpy tools to check if the matrix is unitary using formula U* x U = I
= 3 n
= np.random.normal(size=(n))
v = build_householder(v)
h
print(np.round(h @ h, decimals=2))
def householder_transform(A):
"""
Transforms the matrix A into an upper triangular matrix using Householder reflections.
Parameters:
A (numpy.ndarray): The matrix to be transformed.
Returns:
R (numpy.ndarray): The upper triangular matrix after applying Householder transformations.
"""
= A.copy()
A = A.shape
m, n for j in range(min(m, n)):
# Create the vector x for the current column
= A[j:, j]
x
# Calculate the norm of x and the Householder vector v
= np.linalg.norm(x)
norm_x if norm_x == 0:
continue
= -1 if x[0] < 0 else 1
sign = x.copy()
v 0] += sign * norm_x # Adjust the first element of v for the reflection
v[/= np.linalg.norm(v) # Normalize v
v
# Apply the Householder transformation to A[j:, j:]
-= 2 * np.outer(v, v @ A[j:, j:])
A[j:, j:]
return A
# Example matrix
= np.array([
A 4, 1, -2, 2],
[1, 2, 0, 1],
[-2, 0, 3, -2],
[2, 1, -2, -1]
[=float)
], dtype
= householder_transform(A)
R print("Upper triangular matrix R:\n", R)
Bonus task: check that it also preserves the norm. You can check it for your own custom norm if you created one!
\frac{\Vert x - \widehat{x} \Vert}{\Vert x \Vert} \leq \varepsilon.
\frac{\Vert y - \widehat{y} \Vert}{\Vert y \Vert } = \frac{\Vert U ( x - \widehat{x}) \Vert}{\Vert U x\Vert} \leq \varepsilon.
= np.random.normal(size=(n))
v = v/np.linalg.norm(v)
v = build_householder(v)
h = 0.001
epsilon
= np.random.normal(size=(n))
x = x + epsilon * np.random.normal(size=(n)) # approximaton of x
x_hat = x - x_hat # error of approximation
y
= np.linalg.norm(y)/np.linalg.norm(x)
initial_error = np.linalg.norm(h @ y.T)/np.linalg.norm(h @ x.T)
transformed_error
print("initial error: ", initial_error)
print("transformed error: ", transformed_error)