qr/main.py
2021-06-01 14:48:18 +02:00

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))