import numpy as np

n = 100
a = np.random.rand(n)
b = a + 1e-9 * np.random.randn(n)

# Π‘Π½Π°Ρ‡Π°Π»Π° Π½ΠΎΡ€ΠΌΠΈΡ€ΡƒΠ΅ΠΌ Π²Π΅ΠΊΡ‚ΠΎΡ€ a
a = a / np.linalg.norm(a)

# Π£Π±ΠΈΡ€Π°Π΅ΠΌ ΠΈΠ· b ΠΏΡ€ΠΎΠ΅ΠΊΡ†ΠΈΡŽ Π½Π° a (ΠΏΠΎΠΏΡ‹Ρ‚ΠΊΠ° ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ c ΠΎΡ€Ρ‚ΠΎΠ³ΠΎΠ½Π°Π»ΡŒΠ½Ρ‹ΠΌ ΠΊ a)
c = b - np.dot(b, a) * a
c = c / np.linalg.norm(c)

# БкалярноС ΠΏΡ€ΠΎΠΈΠ·Π²Π΅Π΄Π΅Π½ΠΈΠ΅ c ΠΈ a ΠΏΠΎΠΊΠ° Π½Π΅ идСально ΠΌΠ°Π»ΠΎ:
print(np.dot(c, a))

# Π”Π΅Π»Π°Π΅ΠΌ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΡƒΡŽ ΠΎΡ€Ρ‚ΠΎΠ³ΠΎΠ½Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ:
# Π‘Π½ΠΎΠ²Π° Π²Ρ‹Ρ‡ΠΈΡ‚Π°Π΅ΠΌ ΠΏΡ€ΠΎΠ΅ΠΊΡ†ΠΈΡŽ c Π½Π° a
c = c - np.dot(c, a) * a
c = c / np.linalg.norm(c)

# Π’Π΅ΠΏΠ΅Ρ€ΡŒ скалярноС ΠΏΡ€ΠΎΠΈΠ·Π²Π΅Π΄Π΅Π½ΠΈΠ΅ Π³ΠΎΡ€Π°Π·Π΄ΠΎ мСньшС!
print(np.dot(c, a))
-2.7671064037337878e-08
-6.591949208711867e-17

Π£ΠΏΡ€Π°ΠΆΠ½Π΅Π½ΠΈΠ΅

Π’ этом ΡƒΠΏΡ€Π°ΠΆΠ½Π΅Π½ΠΈΠΈ ΠΌΡ‹ Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠ΅ΠΌ классичСский ΠΈ ΠΌΠΎΠ΄ΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ процСссы Π“Ρ€Π°ΠΌΠ°-Π¨ΠΌΠΈΠ΄Ρ‚Π° для построСния QR-разлоТСния ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρ‹.

Π’Ρ…ΠΎΠ΄: ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Π° A \in \mathbb{R}^{n \times n} Π’Ρ‹Ρ…ΠΎΠ΄: ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρ‹ Q ΠΈ R Ρ‚Π°ΠΊΠΈΠ΅, Ρ‡Ρ‚ΠΎ A = QR, ΠΏΡ€ΠΈ этом Q ΠΎΡ€Ρ‚ΠΎΠ³ΠΎΠ½Π°Π»ΡŒΠ½Π°Ρ, Π° R Π²Π΅Ρ€Ρ…Π½Π΅Ρ‚Ρ€Π΅ΡƒΠ³ΠΎΠ»ΡŒΠ½Π°Ρ. ΠœΡ‹ сфокусируСмся Π½Π° ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Π΅ Q, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ имСя Π΅Ρ‘, ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ Π»Π΅Π³ΠΊΠΎ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ R ΠΏΠΎ Ρ„ΠΎΡ€ΠΌΡƒΠ»Π΅ R = Q^T A.

  • ЧислСнная Π½Π΅ΡƒΡΡ‚ΠΎΠΉΡ‡ΠΈΠ²ΠΎΡΡ‚ΡŒ. ΠšΠ»Π°ΡΡΠΈΡ‡Π΅ΡΠΊΠΈΠΉ процСсс Π“Ρ€Π°ΠΌΠ°-Π¨ΠΌΠΈΠ΄Ρ‚Π° (CGS) ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΠ²ΠΎΠ΄ΠΈΡ‚ΡŒ ΠΊ большим ΠΏΠΎΠ³Ρ€Π΅ΡˆΠ½ΠΎΡΡ‚ΡΠΌ ΠΏΡ€ΠΈ вычислСнии ΠΎΡ€Ρ‚ΠΎΠ½ΠΎΡ€ΠΌΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎΠΉ систСмы Π²Π΅ΠΊΡ‚ΠΎΡ€ΠΎΠ², особСнно ΠΏΡ€ΠΈ ΠΏΠ»ΠΎΡ…ΠΎΠΉ обусловлСнности исходной ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρ‹. Π’ машинной Π°Ρ€ΠΈΡ„ΠΌΠ΅Ρ‚ΠΈΠΊΠ΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ‚ потСря ΠΎΡ€Ρ‚ΠΎΠ³ΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ.

  • ΠœΠΎΠ΄ΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ процСсс (MGS). ИдСя Π·Π°ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ Π²Ρ‹Ρ‡ΠΈΡ‚Π°Ρ‚ΡŒ ΠΏΡ€ΠΎΠ΅ΠΊΡ†ΠΈΠΈ Π½Π° ΡƒΠΆΠ΅ построСнныС ΠΎΡ€Ρ‚ΠΎΠ½ΠΎΡ€ΠΌΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹Π΅ Π²Π΅ΠΊΡ‚ΠΎΡ€Π°, шаг Π·Π° шагом. Π€ΠΎΡ€ΠΌΠ°Π»ΡŒΠ½ΠΎ, Ссли q_1, \dots, q_{k-1} ΡƒΠΆΠ΅ построСны, Ρ‚ΠΎ для k-Π³ΠΎ Π²Π΅ΠΊΡ‚ΠΎΡ€Π°: q_k := a_k, \quad q_k := q_k - (q_k,\,q_1)\,q_1, \quad q_k := q_k - (q_k,\,q_2)\,q_2, \quad \dots Π’ Ρ‚ΠΎΡ‡Π½ΠΎΠΉ Π°Ρ€ΠΈΡ„ΠΌΠ΅Ρ‚ΠΈΠΊΠ΅ это эквивалСнтно классичСскому процСссу, Π½ΠΎ Π² машинной Π°Ρ€ΠΈΡ„ΠΌΠ΅Ρ‚ΠΈΠΊΠ΅ MGS, ΠΊΠ°ΠΊ ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ, Π΄Π°Ρ‘Ρ‚ Π±ΠΎΠ»Π΅Π΅ устойчивоС Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅.

  • Π‘Π»ΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ. Оба Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠ° ΠΈΠΌΠ΅ΡŽΡ‚ Π°ΡΠΈΠΌΠΏΡ‚ΠΎΡ‚ΠΈΡ‡Π΅ΡΠΊΡƒΡŽ ΡΠ»ΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ порядка O(n^2 m), Π³Π΄Π΅ m \times n β€” Ρ€Π°Π·ΠΌΠ΅Ρ€Π½ΠΎΡΡ‚ΡŒ исходной ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρ‹. Для ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚Π½ΠΎΠΉ ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρ‹ n \times n ΡΠ»ΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ O(n^3).

  • QR-Ρ€Π°Π·Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅. По Π³ΠΎΡ‚ΠΎΠ²ΠΎΠΉ ΠΎΡ€Ρ‚ΠΎΠ½ΠΎΡ€ΠΌΠΈΡ€ΠΎΠ²Π°Π½Π½ΠΎΠΉ ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Π΅ Q ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΉΡ‚ΠΈ Π²Π΅Ρ€Ρ…Π½Π΅Ρ‚Ρ€Π΅ΡƒΠ³ΠΎΠ»ΡŒΠ½ΡƒΡŽ R, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΡƒΠΌΠ½ΠΎΠΆΠΈΠ² Q^T A. Π’ΠΎΠ³Π΄Π° A = Q R.

import numpy as np

def hilbert_matrix(n):
    H = np.zeros((n, n), dtype=float)
    for i in range(n):
        for j in range(n):
            H[i, j] = 1.0 / (i + j + 1)
    return H

def generate_matrix(n, type='random'):
    if type == 'random':
        A = np.random.randn(n, n)
    elif type == 'hilbert':
        A = hilbert_matrix(n)
    return A

def classical_gs(A):
    m, n = A.shape
    Q = np.zeros((m, n), dtype=float)
    for k in range(n):
        # Π‘Π΅Ρ€Ρ‘ΠΌ исходный столбСц
        v = A[:, k].copy()
        
        # Π‘Ρ‡ΠΈΡ‚Π°Π΅ΠΌ всС скалярныС произвСдСния \alpha_j = (q_j, v) Π·Π°Ρ€Π°Π½Π΅Π΅
        # (здСсь q_j ΡƒΠΆΠ΅ ΠΎΡ€Ρ‚ΠΎΠ½ΠΎΡ€ΠΌΠΈΡ€ΠΎΠ²Π°Π½Ρ‹ ΠΈΠ· ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰ΠΈΡ… шагов)
        alphas = np.array([np.dot(Q[:, j], v) for j in range(k)])
        
        # Π’Ρ‹Ρ‡ΠΈΡ‚Π°Π΅ΠΌ сумму \sum_j \alpha_j * q_j
        for j in range(k):
            v -= alphas[j] * Q[:, j]
        
        # НормируСм
        norm_v = np.linalg.norm(v)
        Q[:, k] = v / norm_v
    return Q

def modified_gs(A):
    m, n = A.shape
    Q = np.zeros((m, n), dtype=float)
    for k in range(n):
        v = A[:, k].copy()
        # Π’ ΠœΠžΠ”Π˜Π€Π˜Π¦Π˜Π ΠžΠ’ΠΠΠΠžΠœ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠ΅ Π½Π° ΠΊΠ°ΠΆΠ΄ΠΎΠΌ шагС j
        # Π±Π΅Ρ€Ρ‘ΠΌ Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π΅ "свСТСС" v
        for j in range(k):
            alpha = np.dot(Q[:, j], v)
            v -= alpha * Q[:, j]
        Q[:, k] = v / np.linalg.norm(v)
    return Q


# ΠŸΡ€ΠΈΠΌΠ΅Ρ€: провСряСм ΠΎΡ€Ρ‚ΠΎΠ³ΠΎΠ½Π°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ Π½Π° случайной ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Π΅
n = 10
A = generate_matrix(n, type='hilbert')

Q_cgs = classical_gs(A)
Q_mgs = modified_gs(A)

# ΠžΡ†Π΅Π½ΠΈΠ²Π°Π΅ΠΌ "ΠΎΡˆΠΈΠ±ΠΊΡƒ ΠΎΡ€Ρ‚ΠΎΠ³ΠΎΠ½Π°Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ" ΠΊΠ°ΠΊ ||Q^T Q - I|| ΠΏΠΎ ЀробСниусу
err_cgs = np.linalg.norm(Q_cgs.T @ Q_cgs - np.eye(n), ord='fro')
err_mgs = np.linalg.norm(Q_mgs.T @ Q_mgs - np.eye(n), ord='fro')

# ЀробСниусова Π½ΠΎΡ€ΠΌΠ° Π΅Π΄ΠΈΠ½ΠΈΡ‡Π½ΠΎΠΉ ΠΌΠ°Ρ‚Ρ€ΠΈΡ†Ρ‹ I (это sqrt(n))
norm_I_fro = np.linalg.norm(np.eye(n), ord='fro')

rel_cgs_fro = err_cgs / norm_I_fro
rel_mgs_fro = err_mgs / norm_I_fro

print("ΠΠ±ΡΠΎΠ»ΡŽΡ‚Π½Π°Ρ ошибка (CGS) =", err_cgs)
print("ΠΠ±ΡΠΎΠ»ΡŽΡ‚Π½Π°Ρ ошибка (MGS) =", err_mgs)
print("ΠžΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ ошибка (CGS) =", rel_cgs_fro)
print("ΠžΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ ошибка (MGS) =", rel_mgs_fro)
ΠΠ±ΡΠΎΠ»ΡŽΡ‚Π½Π°Ρ ошибка (CGS) = 3.4653383412436143
ΠΠ±ΡΠΎΠ»ΡŽΡ‚Π½Π°Ρ ошибка (MGS) = 8.763548618214397e-05
ΠžΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ ошибка (CGS) = 1.095836202143963
ΠžΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ ошибка (MGS) = 2.7712774019178854e-05