183 lines
5.6 KiB
Python
183 lines
5.6 KiB
Python
from copy import deepcopy
|
|
from matplotlib import pyplot as plt
|
|
from typing import Tuple
|
|
|
|
import numpy as np
|
|
from scipy.linalg import solve
|
|
|
|
"""
|
|
Class that performs qr decomposition. Use methods:
|
|
perform_householder_QR,
|
|
perform_givens_QR
|
|
both accept np.ndarray that fulfils m >= n condition is 2d, and contains real numbers.
|
|
"""
|
|
|
|
|
|
class QR:
|
|
def __init__(self) -> None:
|
|
pass
|
|
|
|
"""
|
|
Checks if calculated matrices fulfill QR decomposition conditions, that is:
|
|
A = QR , where Q -> Q * Qt = I and R is a upper triangular matrix
|
|
"""
|
|
|
|
def __check_condition(self, Q: np.matrix, R: np.matrix) -> bool:
|
|
if not np.allclose(R, np.triu(R)):
|
|
print("R matrix is not upper triangle.")
|
|
return False
|
|
I = np.identity(Q.shape[1])
|
|
comparison = np.equal(np.matmul(np.transpose(Q), Q), I)
|
|
if not comparison.all():
|
|
print("Q matrix is not orthogonal.")
|
|
return False
|
|
return True
|
|
|
|
"""
|
|
Checks if given matrix is 2d, m >= n and filled with real numbers
|
|
"""
|
|
|
|
def __check_pre_conditions(self, matrix: np.ndarray) -> bool:
|
|
if not matrix.shape[0] >= matrix.shape[1]:
|
|
print("Matrix is m is lesser than n.")
|
|
return False
|
|
if len(matrix.shape) != 2:
|
|
print("Matrix is not 2D.")
|
|
return False
|
|
if not np.isreal(matrix).all():
|
|
print("Matrix doesn't contain all real numbers.")
|
|
return False
|
|
return True
|
|
|
|
"""
|
|
Method that performs Householder Transformation QR, accepts 2D, real numbers matrix, that
|
|
fulfills m >= n condition. Return Q and R matrices.
|
|
"""
|
|
|
|
def perform_householder_QR(self, matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
if self.__check_pre_conditions(matrix):
|
|
return self.__householder_qr(matrix)
|
|
else:
|
|
print("Incorrect type.")
|
|
raise Exception
|
|
|
|
def __householder(self, matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
v = matrix / (matrix[0] + np.copysign(np.linalg.norm(matrix), matrix[0]))
|
|
v[0] = 1
|
|
tau = 2 / (v.T @ v)
|
|
return v, tau
|
|
|
|
def __householder_qr(self, matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
m, n = matrix.shape
|
|
R = matrix.copy()
|
|
Q = np.identity(m)
|
|
|
|
for j in range(0, n):
|
|
v, tau = self.__householder(R[j:, j, np.newaxis])
|
|
|
|
H = np.identity(m)
|
|
H[j:, j:] -= tau * (v @ v.T)
|
|
R = H @ R
|
|
Q = H @ Q
|
|
|
|
return Q[:n].T, np.triu(R[:n])
|
|
|
|
def __givens_qr(self, matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
m, n = matrix.shape
|
|
R = matrix
|
|
Q = np.eye(m)
|
|
G = np.zeros((2, 2))
|
|
for j in range(n):
|
|
for i in reversed(range(j + 1, m)):
|
|
a, b = R[i - 1, j], R[i, j]
|
|
G = np.asarray([[a, b], [-b, a]]) / np.sqrt(a ** 2 + b ** 2)
|
|
R[i - 1:i + 1, j] = G @ R[i - 1:i + 1, j]
|
|
Q[i - 1:i + 1, :] = G @ Q[i - 1:i + 1, :]
|
|
|
|
return Q.T, R
|
|
|
|
"""
|
|
Method that performs Givens Rotation QR, accepts 2D, real numbers matrix, that
|
|
fulfills m >= n condition. Return Q and R matrices.
|
|
"""
|
|
|
|
def perform_givens_QR(self, matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
if self.__check_pre_conditions(matrix):
|
|
return self.__givens_qr(matrix)
|
|
else:
|
|
print("Incorrect type.")
|
|
raise Exception
|
|
|
|
def solve_least_squares(self, A: np.ndarray, b: np.array, is_givens):
|
|
if is_givens:
|
|
Q, R = self.perform_givens_QR(A)
|
|
else:
|
|
Q, R = self.perform_householder_QR(A)
|
|
x = solve(R, np.dot(Q.T, b))
|
|
return x
|
|
|
|
def __design_matrix(self, A: np.ndarray):
|
|
return np.hstack((np.ones(A.shape[0]).reshape(-1, 1), A[:, :-1]))
|
|
|
|
def fit_poly(self, A: np.ndarray, is_givens: bool):
|
|
return self.solve_least_squares(
|
|
np.dot(self.__design_matrix(A), self.__design_matrix(A).T),
|
|
A[:, -1:].reshape(-1, 1),
|
|
is_givens=is_givens)
|
|
|
|
|
|
"""
|
|
Usage example
|
|
"""
|
|
if __name__ == "__main__":
|
|
qr = QR()
|
|
matrix = np.matrix('0 0 0; 1 1 2; 1 2 4; 3 3 5; 5 6 7; 8 9 10')
|
|
matrix = np.asarray(matrix)
|
|
print(matrix)
|
|
a = deepcopy(matrix)
|
|
b = deepcopy(matrix)
|
|
c = deepcopy(matrix)
|
|
Q, R = qr.perform_householder_QR(a)
|
|
print("Householder:")
|
|
print(Q)
|
|
print(R)
|
|
print("Givens")
|
|
Q, R = qr.perform_givens_QR(b)
|
|
print(Q)
|
|
print(R)
|
|
# dla porównania
|
|
print("Numpy")
|
|
Q, R = np.linalg.qr(c)
|
|
print(Q)
|
|
print(R)
|
|
print('solve least squares')
|
|
print(matrix[:2, :-1])
|
|
print(matrix[:2, -1:])
|
|
print(qr.solve_least_squares(matrix[:2, :-1], matrix[:2, -1:], is_givens=True))
|
|
|
|
def PolyCoefficients(x1,x2, coeffs):
|
|
""" Returns a polynomial for ``x`` values for the ``coeffs`` provided.
|
|
The coefficients must be in ascending order (``x**0`` to ``x**o``).
|
|
"""
|
|
o = len(coeffs)
|
|
y = 0
|
|
for i in range(o):
|
|
y += coeffs[i] * x1 ** i + coeffs[i] * x2 ** i
|
|
return y
|
|
|
|
|
|
def f(a, b):
|
|
return a + 2 * (b ** 2)
|
|
|
|
|
|
x1 = np.asarray(np.random.randint(0,10,5))
|
|
x2 = np.asarray(np.random.randint(0,10,5))
|
|
y = f(x1, x2)
|
|
mat = np.asarray([x1, x2, y])
|
|
plt3d = plt.figure().gca(projection='3d')
|
|
#xx,yy = np.meshgrid(np.linspace(0,10,11),np.linspace(0,10,11))
|
|
xx, yy = np.meshgrid(range(10), range(10))
|
|
plt3d.plot_surface(xx, yy, f(xx, yy), alpha=0.2)
|
|
plt3d.plot_surface(xx, yy, PolyCoefficients(xx,yy, qr.fit_poly(mat.T, is_givens=True)), alpha=0.2)
|
|
plt.show()
|
|
print(qr.fit_poly(mat.T, is_givens=True)) |