first commit
This commit is contained in:
166
project1/Project1_Q3.py
Normal file
166
project1/Project1_Q3.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# householder function from question 2
|
||||
def householder_qr(A: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Perform Householder QR factorization on matrix A to obtain orthogonal matrix Q.
|
||||
|
||||
Parameters:
|
||||
A (np.ndarray): The input matrix to be orthogonalized.
|
||||
|
||||
Returns:
|
||||
tuple[np.ndarray, np.ndarray]: The orthogonal matrix Q and upper triangular matrix R such that A = QR.
|
||||
"""
|
||||
m, n = A.shape
|
||||
Q = np.eye(m, dtype=A.dtype)
|
||||
R = A.copy()
|
||||
|
||||
for k in range(n):
|
||||
# Extract the vector x
|
||||
# Target: x -> [||x||, 0, 0, ..., 0]^T
|
||||
x = R[k:, k] # x = [a_k, a_(k+1), ..., a_m]^T
|
||||
s = np.sign(x[0]) if x[0] != 0 else 1
|
||||
e = np.zeros_like(x) # e = [||x||, 0, 0, ..., 0]^T
|
||||
e[0] = s * np.linalg.norm(x)
|
||||
# Compute the Householder vector
|
||||
# x - e is the reflection plane normal
|
||||
u = x - e
|
||||
if np.linalg.norm(u) < 1e-14:
|
||||
continue # Skip the transformation if u is too small
|
||||
v = u / np.linalg.norm(u)
|
||||
# Apply the Householder transformation to R[k:, k:]
|
||||
R[k:, k:] -= 2 * np.outer(v, v.conj().T @ R[k:, k:])
|
||||
# Update Q
|
||||
# Q_k = I - 2 v v^*
|
||||
Q_k = np.eye(m, dtype=A.dtype)
|
||||
Q_k[k:, k:] -= 2 * np.outer(v, v.conj().T)
|
||||
Q = Q @ Q_k
|
||||
|
||||
return Q, R
|
||||
|
||||
def householder_lstsq(A: np.ndarray, b: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Solve the least squares problem Ax = b using Householder reflections.
|
||||
|
||||
Parameters:
|
||||
A (np.ndarray): The input matrix A.
|
||||
b (np.ndarray): The right-hand side vector b.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The solution vector x.
|
||||
"""
|
||||
"""
|
||||
Ax = b, A = QR
|
||||
Rx = Q^*b
|
||||
x = R^{-1}Q^*b
|
||||
R is upper triangular
|
||||
Q is orthogonal
|
||||
"""
|
||||
m, n = A.shape # m is N + 1, n is n + 1
|
||||
Q, R = householder_qr(A)
|
||||
# Compute Q^*b
|
||||
Qt_b = Q.conj().T @ b
|
||||
# Solve Rx = Q^*b using back substitution
|
||||
x = np.zeros(n, dtype=A.dtype)
|
||||
for i in range(n-1, -1, -1):
|
||||
x[i] = (Qt_b[i] - R[i, i+1:] @ x[i+1:]) / R[i, i]
|
||||
return x
|
||||
|
||||
def f1(t: float) -> float:
|
||||
"""
|
||||
f1(t) = 1 / (1 + t^2)
|
||||
"""
|
||||
return 1.0 / (1.0 + t**2)
|
||||
|
||||
def build_A(N: int, n: int, f: callable) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
Build the matrix A for the least squares problem.
|
||||
|
||||
Parameters:
|
||||
n + 1 (int): The number of columns in the matrix A.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The constructed matrix A.
|
||||
"""
|
||||
"""
|
||||
{t_j}j=0->N, t_0=-5, t_N=5, t_j divides [-5, 5] equally
|
||||
A = [[1, t_0, t_0^2, t_0^3, ..., t_0^n],
|
||||
[1, t_1, t_1^2, t_1^3, ..., t_1^n],
|
||||
...
|
||||
[1, t_N, t_N^2, t_N^3, ..., t_N^n]] (N + 1) x (n + 1)
|
||||
|
||||
b = [f(t_0), f(t_1), ..., f(t_N)]^T (N + 1) x 1
|
||||
x = [a_0, a_1, a_2, ..., a_n]^T (n + 1) x 1
|
||||
Ax = b, x is the coeffs of the polynomial
|
||||
"""
|
||||
A = np.zeros((N + 1, n + 1), dtype=float)
|
||||
t = np.linspace(-5, 5, N + 1)
|
||||
for i in range(N + 1):
|
||||
A[i, :] = [t[i]**j for j in range(n + 1)]
|
||||
b = np.array([f(t_i) for t_i in t])
|
||||
return A, b
|
||||
|
||||
def question_3c():
|
||||
Ns = [30, 30, 30]
|
||||
ns = [5, 15, 30]
|
||||
# p(t) = a_n t^n + a_(n-1) t^(n-1) + ... + a_1 t + a_0
|
||||
# plot f(t) and p(t) on the same figure for each (N, n)
|
||||
t = np.linspace(-5, 5, 1000)
|
||||
f_t = f1(t)
|
||||
plt.figure(figsize=(15, 5))
|
||||
for i in range(len(Ns)):
|
||||
N = Ns[i]
|
||||
n = ns[i]
|
||||
A = np.zeros((N + 1, n + 1), dtype=float)
|
||||
A, b = build_A(N, n, f1)
|
||||
x = householder_lstsq(A, b)
|
||||
p_t = sum([x[j] * t**j for j in range(n + 1)])
|
||||
plt.subplot(1, 3, i + 1)
|
||||
plt.plot(t, f_t, label='f(t)', color='blue')
|
||||
plt.plot(t, p_t, label='p(t)', color='red', linestyle='--')
|
||||
plt.title(f'N={N}, n={n}')
|
||||
plt.xlabel('t')
|
||||
plt.ylabel('y')
|
||||
plt.legend()
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.suptitle('Least Squares Polynomial Approximation using Householder QR')
|
||||
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
||||
plt.savefig('Project1_A3c_least_squares_approximation.png')
|
||||
#plt.show()
|
||||
|
||||
def f2(n:int, t: float) -> float:
|
||||
"""
|
||||
f2(t) = t^n + t^(n-1) + ... + t^2 + t + 1
|
||||
"""
|
||||
return sum([t**i for i in range(n + 1)])
|
||||
|
||||
def question_3e():
|
||||
# n = 4, 6, 8, 10, ..., 20
|
||||
ns = list(range(4, 21, 2))
|
||||
# N = 2n
|
||||
# quantity is log10||(a_0-1,...,a_n-1)||_2)
|
||||
losses = []
|
||||
for n in ns:
|
||||
N = 2 * n
|
||||
A, b = build_A(N, n, lambda t: f2(n, t))
|
||||
x = householder_lstsq(A, b)
|
||||
true_coeffs = np.array([1.0 for _ in range(n + 1)])
|
||||
loss = np.linalg.norm(x - true_coeffs)
|
||||
losses.append(np.log10(loss))
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.plot(ns, losses, marker='o')
|
||||
plt.xlabel('Polynomial Degree n')
|
||||
plt.ylabel('log10(||a - true_coeffs||_2)')
|
||||
plt.title('Coefficient Error vs Polynomial Degree using Householder QR')
|
||||
plt.xticks(ns)
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.savefig('Project1_A3e_coefficient_error.png')
|
||||
#plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
question_3c()
|
||||
question_3e()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user