Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Lecture 14. 직교 벡터와 직교 부분공간 — 파이썬 실습

Orthogonal Vectors and Subspaces — 실습

L14 서술 파트에서 내적을 도입하자 L10의 지도에 직각이 나타났다. 이 노트북에서는 그 직각을 실제로 재 본다. 네 부분공간의 기저를 뽑아 내적표를 만들면 전부 0이 나와야 하고, 무작위 행렬 수십 개를 넣어도 한 번도 어긋나지 않아야 한다.

서술 파트의 내용여기서 확인하는 방법
피타고라스 \Leftrightarrow 내적 0두 값의 차가 정확히 2xTy2\,\vv{x}^{\mathsf{T}}\vv{y} 인지 본다
바닥과 벽두 기저의 내적표에 0이 아닌 칸이 남는다
행공간 \perp 영공간orth(A.T).T @ null_space(A) 가 영행렬
직교여공간차원의 합과 유일한 분해
N(ATA)=N(A)\Nul(A^{\mathsf{T}}A) = \Nul(A)무작위 행렬로 랭크까지 대조

0. 준비

import numpy as np
import plotly.graph_objects as go
from scipy.linalg import null_space, orth

from linalg_viz import (COLORS, arrow, layout3d, line, plane, show_matrix,
                        spin_figure)

np.set_printoptions(precision=3, suppress=True)
rng = np.random.default_rng(14)
print("numpy", np.__version__)
numpy 2.5.2

1. 내적, 길이, 피타고라스

내적은 @ 하나로 끝난다. 길이는 자기 자신과의 내적의 제곱근이다.

x = np.array([3.0, 0.0])
y = np.array([0.0, 4.0])

print("x . y      =", x @ y)
print("y . x      =", y @ x, "  (순서를 바꿔도 같다)")
print("x . x      =", x @ x, "  = |x|^2")
print("|x|        =", np.linalg.norm(x))
print("numpy 와 일치 :", np.isclose(x @ x, np.linalg.norm(x) ** 2))
x . y      = 0.0
y . x      = 0.0   (순서를 바꿔도 같다)
x . x      = 9.0   = |x|^2
|x|        = 3.0
numpy 와 일치 : True

서술 파트에서 x+y2x2y2=2xTy\|\vv{x}+\vv{y}\|^2 - \|\vv{x}\|^2 - \|\vv{y}\|^2 = 2\,\vv{x}^{\mathsf{T}}\vv{y} 를 유도했다. 어긋나는 양이 정확히 내적의 두 배인지 확인해 보자.

def 피타고라스검사(x, y, 이름=""):
    """두 변의 제곱합과 빗변의 제곱을 비교하고, 그 차를 내적과 대조한다."""
    왼쪽 = x @ x + y @ y
    오른쪽 = (x + y) @ (x + y)
    print(f"{이름}")
    print(f"  |x|^2 + |y|^2 = {왼쪽:.4f}")
    print(f"  |x + y|^2     = {오른쪽:.4f}")
    print(f"  차이          = {오른쪽 - 왼쪽:.4f}")
    print(f"  2 (x . y)     = {2 * (x @ y):.4f}   -> 같은가 "
          f"{np.isclose(오른쪽 - 왼쪽, 2 * (x @ y))}")
피타고라스검사(np.array([3.0, 0]), np.array([0.0, 4]), "직교하는 경우")
print()
피타고라스검사(np.array([3.0, 0]), np.array([2.0, 3]), "직교하지 않는 경우")
직교하는 경우
  |x|^2 + |y|^2 = 25.0000
  |x + y|^2     = 25.0000
  차이          = 0.0000
  2 (x . y)     = 0.0000   -> 같은가 True

직교하지 않는 경우
  |x|^2 + |y|^2 = 22.0000
  |x + y|^2     = 34.0000
  차이          = 12.0000
  2 (x . y)     = 12.0000   -> 같은가 True

그림을 그릴 수 없는 차원에서도 똑같다. 직교는 각도가 아니라 등식이기 때문이다.

n = 50
u = rng.normal(size=n)
v = rng.normal(size=n)
v직교 = v - (v @ u) / (u @ u) * u          # u 성분을 빼면 u 와 직교한다

for 이름, 짝 in [("무작위 두 벡터", (u, v)), ("u 성분을 뺀 뒤", (u, v직교))]:
    a, b = 짝
    print(f"{이름:>16} : x . y = {a @ b:+.3e},   "
          f"차이 - 2(x.y) = {(a + b) @ (a + b) - a @ a - b @ b - 2 * (a @ b):+.2e}")
        무작위 두 벡터 : x . y = -3.459e+00,   차이 - 2(x.y) = -2.75e-14
       u 성분을 뺀 뒤 : x . y = -1.998e-15,   차이 - 2(x.y) = -1.02e-14

2. 바닥과 벽

두 부분공간이 직교하는지 보려면 양쪽 기저의 모든 짝을 내적해 보면 된다. 기저끼리 전부 0이면 그 결합끼리도 전부 0이기 때문이다.

def 내적표(V, W):
    """열이 기저인 두 행렬을 받아 기저끼리의 내적표를 만든다."""
    return V.T @ W
바닥 = np.array([[1.0, 0], [0, 1], [0, 0]])      # z = 0 : 열이 (1,0,0), (0,1,0)
벽 = np.array([[1.0, 0], [0, 0], [0, 1]])        # y = 0 : 열이 (1,0,0), (0,0,1)

print(show_matrix(내적표(바닥, 벽), "바닥 기저 . 벽 기저"))
print("전부 0 인가 :", np.allclose(내적표(바닥, 벽), 0))
print()
print("겹치는 벡터 v = (1,0,0) 이 양쪽에 있다.")
v = np.array([1.0, 0, 0])
print("  v . v =", v @ v, " -> 0 이 아니므로 직교 부분공간일 수 없다")
바닥 기저 . 벽 기저
[  1   0 ]
[  0   0 ]
전부 0 인가 : False

겹치는 벡터 v = (1,0,0) 이 양쪽에 있다.
  v . v = 1.0  -> 0 이 아니므로 직교 부분공간일 수 없다

정말 직교하는 짝과 비교해 보자. 바닥과 zz 축은 직교한다.

z축 = np.array([[0.0], [0.0], [1.0]])

print(show_matrix(내적표(바닥, z축), "바닥 기저 . z축"))
print("전부 0 인가 :", np.allclose(내적표(바닥, z축), 0))
print("공유하는 벡터가 있는가 :",
      np.linalg.matrix_rank(np.column_stack([바닥, z축])) < 3)
바닥 기저 . z축
[  0 ]
[  0 ]
전부 0 인가 : True
공유하는 벡터가 있는가 : False

3. 행공간과 영공간

서술 파트의 예 A=[111123]A = \begin{bmatrix} 1 & 1 & 1 \\ 1 & 2 & 3\end{bmatrix} 로 시작한다.

A = np.array([[1.0, 1, 1],
              [1.0, 2, 3]])

행공간 = orth(A.T)                 # 열이 행공간의 정규직교 기저
영공간 = null_space(A)             # 열이 영공간의 정규직교 기저

print(show_matrix(행공간, "행공간의 기저"))
print(show_matrix(영공간, "영공간의 기저"))
print("차원 :", 행공간.shape[1], "+", 영공간.shape[1], "=",
      행공간.shape[1] + 영공간.shape[1], " (n =", A.shape[1], ")")
행공간의 기저
[  -0.323    0.854 ]
[  -0.548    0.183 ]
[  -0.772   -0.487 ]
영공간의 기저
[   0.408 ]
[  -0.816 ]
[   0.408 ]
차원 : 2 + 1 = 3  (n = 3 )
print(show_matrix(내적표(행공간, 영공간), "행공간 기저 . 영공간 기저"))
print("전부 0 인가 :", np.allclose(내적표(행공간, 영공간), 0, atol=1e-12))
행공간 기저 . 영공간 기저
[  -5.55e-17 ]
[  -8.33e-17 ]
전부 0 인가 : True

서술 파트의 증명은 Ax=0A\vv{x} = \vv{0} 을 행별로 읽은 것이 전부였다. 그러니 사실 A @ 영공간 자체가 이미 그 내적표이다. 각 성분이 (행) \cdot (영공간 벡터)이다.

print(show_matrix(A @ 영공간, "A @ 영공간  =  (각 행) . (영공간 기저)"))
print("전부 0 인가 :", np.allclose(A @ 영공간, 0, atol=1e-12))
print()
print("영공간 기저를 정수로 보면 :", np.round(영공간[:, 0] / 영공간[0, 0], 6))
A @ 영공간  =  (각 행) . (영공간 기저)
[         0 ]
[  5.55e-17 ]
전부 0 인가 : True

영공간 기저를 정수로 보면 : [ 1. -2.  1.]

행공간에서 아무 벡터나, 영공간에서 아무 벡터나 뽑아 내적해도 0이어야 한다. 계수를 무작위로 바꿔 가며 여러 번 해 보자.

최대 = 0.0
for _ in range(2000):
    v = 행공간 @ rng.normal(size=행공간.shape[1])
    w = 영공간 @ rng.normal(size=영공간.shape[1])
    최대 = max(최대, abs(v @ w))

print(f"2000번 중 가장 큰 내적의 절댓값 : {최대:.2e}")
print("0 이라고 봐도 되는가 :", 최대 < 1e-12)
2000번 중 가장 큰 내적의 절댓값 : 6.66e-16
0 이라고 봐도 되는가 : True

4. 열공간과 좌영공간

증명은 AA 자리에 ATA^{\mathsf{T}} 를 넣은 것뿐이었다. 코드도 그렇다.

def 직교확인(A, 이름="A"):
    """네 부분공간의 기저를 뽑아 두 쌍의 내적표가 영행렬인지 확인한다."""
    m, n = A.shape
    r = np.linalg.matrix_rank(A)
    쌍 = {
        "행공간 . 영공간": (orth(A.T), null_space(A)),
        "열공간 . 좌영공간": (orth(A), null_space(A.T)),
    }
    print(f"[{이름}]  {m} x {n},  rank {r}")
    for 이름칸, (V, W) in 쌍.items():
        if V.size == 0 or W.size == 0:
            print(f"  {이름칸:>18} : 한쪽이 비어 있다 (차원 0)")
            continue
        표 = V.T @ W
        print(f"  {이름칸:>18} : 크기 {표.shape}, "
              f"가장 큰 절댓값 {np.abs(표).max():.2e}, 전부 0 {np.allclose(표, 0, atol=1e-10)}")
    return r
_ = 직교확인(A, "L14 의 예")
print()
B = np.array([[1.0, 2, 2, 2],
              [2.0, 4, 6, 8],
              [3.0, 6, 8, 10]])
_ = 직교확인(B, "L7 의 행렬")
[L14 의 예]  2 x 3,  rank 2
           행공간 . 영공간 : 크기 (2, 1), 가장 큰 절댓값 8.33e-17, 전부 0 True
          열공간 . 좌영공간 : 한쪽이 비어 있다 (차원 0)

[L7 의 행렬]  3 x 4,  rank 2
           행공간 . 영공간 : 크기 (2, 2), 가장 큰 절댓값 4.31e-16, 전부 0 True
          열공간 . 좌영공간 : 크기 (2, 1), 가장 큰 절댓값 2.22e-16, 전부 0 True

AA 쪽에서 두 번째 쌍이 비어 있다고 나온다. AA2×32 \times 3 이고 랭크가 2라 C(A)=R2\Col(A) = \R^2 이고 N(AT)={0}\Nul(A^{\mathsf{T}}) = \{\vv{0}\} 이기 때문이다. 직교할 상대가 영벡터뿐이면 확인할 것도 없다.

무작위 행렬 여러 개에 넣어도 한 번도 어긋나지 않아야 한다.

print(f"{'m x n':>8}{'rank':>6}{'행 . 영':>12}{'열 . 좌영':>12}")
for _ in range(10):
    m_ = int(rng.integers(2, 7))
    n_ = int(rng.integers(2, 7))
    k = int(rng.integers(1, min(m_, n_) + 1))
    M = (rng.integers(-3, 4, (m_, k)) @ rng.integers(-3, 4, (k, n_))).astype(float)
    if np.linalg.matrix_rank(M) == 0:
        continue

    쌍1 = (orth(M.T), null_space(M))
    쌍2 = (orth(M), null_space(M.T))
    값 = []
    for V, W in (쌍1, 쌍2):
        값.append("(빈 공간)" if V.size == 0 or W.size == 0
                  else f"{np.abs(V.T @ W).max():.1e}")
    print(f"{f'{m_} x {n_}':>8}{np.linalg.matrix_rank(M):>6}{값[0]:>12}{값[1]:>12}")
   m x n  rank       행 . 영      열 . 좌영
   2 x 4     1     9.7e-17     5.6e-17
   6 x 2     1     0.0e+00     5.6e-17
   4 x 2     1     1.1e-16     1.4e-16
   3 x 6     2     4.2e-17     5.6e-17
   4 x 5     1     4.1e-16     1.1e-16
   3 x 2     2      (빈 공간)     0.0e+00
   2 x 3     1     7.5e-17     5.6e-17
   5 x 2     2      (빈 공간)     1.3e-16
   6 x 3     1     1.9e-16     4.3e-16
   2 x 5     2     2.4e-16      (빈 공간)

5. 직교여공간과 유일한 분해

행공간의 기저와 영공간의 기저를 한데 모으면 Rn\R^n 의 기저가 된다. 그러면 아무 벡터나 두 조각으로 갈리고, 갈리는 방법은 하나뿐이다.

def 쪼개기(A, x):
    """x 를 행공간 성분과 영공간 성분으로 나눈다.

    두 기저를 이어 붙여 만든 정사각행렬로 좌표를 풀면 된다.
    (다음 강의에서 이보다 직접적인 공식을 배운다.)
    """
    Q, N = orth(A.T), null_space(A)
    B = np.column_stack([Q, N])            # n x n, 가역이다
    좌표 = np.linalg.solve(B, x)
    r = Q.shape[1]
    return Q @ 좌표[:r], N @ 좌표[r:]
x = np.array([0.0, -2.0, 2.0])
x_r, x_n = 쪼개기(A, x)

print("x   =", x)
print("x_r =", x_r, "  (행공간 성분)")
print("x_n =", x_n, "  (영공간 성분)")
print()
print("합이 x 인가        :", np.allclose(x_r + x_n, x))
print("x_n 이 영공간인가  :", np.allclose(A @ x_n, 0))
print("x_r 이 영공간과 직교 :", np.allclose(x_r @ 영공간, 0, atol=1e-12))
print("서로 직교한가      :", np.isclose(x_r @ x_n, 0, atol=1e-12))
x   = [ 0. -2.  2.]
x_r = [-1. -0.  1.]   (행공간 성분)
x_n = [ 1. -2.  1.]   (영공간 성분)

합이 x 인가        : True
x_n 이 영공간인가  : True
x_r 이 영공간과 직교 : True
서로 직교한가      : True

서술 파트에서 xr=(1,0,1)\vv{x}_r = (-1, 0, 1), xn=(1,2,1)\vv{x}_n = (1, -2, 1) 이라 했다. 맞는지 보자.

print("손으로  : x_r = [-1  0  1] ,  x_n = [ 1 -2  1]")
print("코드로  : x_r =", np.round(x_r, 10) + 0.0,
      ",  x_n =", np.round(x_n, 10) + 0.0)
print("같은가 :", np.allclose(x_r, [-1, 0, 1]) and np.allclose(x_n, [1, -2, 1]))
손으로  : x_r = [-1  0  1] ,  x_n = [ 1 -2  1]
코드로  : x_r = [-1.  0.  1.] ,  x_n = [ 1. -2.  1.]
같은가 : True

유일성을 확인해 보자. 행공간의 기저를 다른 것으로 바꿔도 나오는 조각은 같아야 한다.

M = rng.normal(size=(2, 2))
while abs(np.linalg.det(M)) < 0.3:
    M = rng.normal(size=(2, 2))

다른기저 = orth(A.T) @ M                    # 같은 행공간, 다른 기저
B2 = np.column_stack([다른기저, 영공간])
좌표2 = np.linalg.solve(B2, x)
x_r2 = 다른기저 @ 좌표2[:2]

print(show_matrix(다른기저, "행공간의 다른 기저"))
print("이 기저로 구한 x_r :", np.round(x_r2, 10) + 0.0)
print("처음 것과 같은가   :", np.allclose(x_r, x_r2))
행공간의 다른 기저
[  -0.904    -0.53 ]
[   0.108    0.201 ]
[    1.12    0.933 ]
이 기저로 구한 x_r : [-1.  0.  1.]
처음 것과 같은가   : True

AA 는 영공간 성분을 보지 않는다. 그래서 Ax=AxrA\vv{x} = A\vv{x}_r 이다.

print("A x   =", A @ x)
print("A x_r =", A @ x_r)
print("A x_n =", A @ x_n, "  (영벡터)")
print("A x = A x_r 인가 :", np.allclose(A @ x, A @ x_r))
A x   = [0. 2.]
A x_r = [-0.  2.]
A x_n = [0. 0.]   (영벡터)
A x = A x_r 인가 : True

6. ATAA^{\mathsf{T}}A

ATAA^{\mathsf{T}}A 는 언제나 대칭이고, 영공간과 랭크가 AA 와 같다.

G = A.T @ A
print(show_matrix(G, "A^T A"))
print("대칭인가 :", np.allclose(G, G.T))
print("rank(A)     :", np.linalg.matrix_rank(A))
print("rank(A^T A) :", np.linalg.matrix_rank(G))
print()
print("두 영공간이 같은 공간인가 :",
      np.linalg.matrix_rank(np.column_stack([null_space(A), null_space(G)]))
      == null_space(A).shape[1])
A^T A
[   2    3    4 ]
[   3    5    7 ]
[   4    7   10 ]
대칭인가 : True
rank(A)     : 2
rank(A^T A) : 2

두 영공간이 같은 공간인가 : True

증명의 핵심은 xTATAx=Ax2\vv{x}^{\mathsf{T}}A^{\mathsf{T}}A\vv{x} = \|A\vv{x}\|^2 이었다. 숫자로 확인해 보자.

for _ in range(5):
    z = rng.normal(size=3)
    왼쪽 = z @ (A.T @ A) @ z
    오른쪽 = np.linalg.norm(A @ z) ** 2
    print(f"x^T A^T A x = {왼쪽:8.4f}    |A x|^2 = {오른쪽:8.4f}    "
          f"같은가 {np.isclose(왼쪽, 오른쪽)}")
x^T A^T A x =   2.1802    |A x|^2 =   2.1802    같은가 True
x^T A^T A x =   2.9629    |A x|^2 =   2.9629    같은가 True
x^T A^T A x =   9.7339    |A x|^2 =   9.7339    같은가 True
x^T A^T A x =   0.3655    |A x|^2 =   0.3655    같은가 True
x^T A^T A x =  60.1133    |A x|^2 =  60.1133    같은가 True

무작위 행렬로 랭크와 영공간 차원이 언제나 맞아떨어지는지 본다.

print(f"{'m x n':>8}{'rank A':>8}{'rank A^T A':>12}{'dim N(A)':>10}"
      f"{'dim N(A^T A)':>14}{'가역':>7}")
for _ in range(10):
    m_ = int(rng.integers(2, 8))
    n_ = int(rng.integers(2, 6))
    k = int(rng.integers(1, min(m_, n_) + 1))
    M = (rng.integers(-3, 4, (m_, k)) @ rng.integers(-3, 4, (k, n_))).astype(float)
    G_ = M.T @ M
    rM, rG = np.linalg.matrix_rank(M), np.linalg.matrix_rank(G_)
    print(f"{f'{m_} x {n_}':>8}{rM:>8}{rG:>12}"
          f"{null_space(M).shape[1]:>10}{null_space(G_).shape[1]:>14}"
          f"{str(rG == n_):>7}")
   m x n  rank A  rank A^T A  dim N(A)  dim N(A^T A)     가역
   3 x 4       3           3         1             1  False
   7 x 2       2           2         0             0   True
   5 x 2       2           2         0             0   True
   4 x 5       2           2         3             3  False
   7 x 4       1           1         3             3  False
   6 x 5       4           4         1             1  False
   2 x 5       2           2         3             3  False
   4 x 4       4           4         0             0   True
   6 x 2       2           2         0             0   True
   3 x 3       2           2         1             1  False

가역인 줄은 rank A^T Ann 과 같은 줄, 곧 AA 의 열이 독립인 줄뿐이다. 열이 종속이면 ATAA^{\mathsf{T}}A 도 특이하다.

나쁜A = np.array([[1.0, 1], [1.0, 1]])
나쁜G = 나쁜A.T @ 나쁜A

print(show_matrix(나쁜A, "A  (두 열이 같다)"))
print(show_matrix(나쁜G, "A^T A"))
print("rank(A)     :", np.linalg.matrix_rank(나쁜A))
print("rank(A^T A) :", np.linalg.matrix_rank(나쁜G))
print("det(A^T A)  :", np.linalg.det(나쁜G))
print("가역인가 :", np.linalg.matrix_rank(나쁜G) == 나쁜A.shape[1])
A  (두 열이 같다)
[  1   1 ]
[  1   1 ]
A^T A
[  2   2 ]
[  2   2 ]
rank(A)     : 1
rank(A^T A) : 1
det(A^T A)  : 0.0
가역인가 : False

7. 직각을 여러 각도에서 보기

3차원 그림은 보는 방향에 따라 직각이 직각으로 보이지 않는다. 돌려 가며 확인해 보자. 평면이 행공간, 회색 직선이 영공간이다.

법선 = np.array([1.0, -2.0, 1.0])          # 영공간의 방향

그림요소 = [
    plane(normal=법선, extent=2.4, color=COLORS["colspace"],
          opacity=0.35, name="행공간"),
    line(법선, extent=1.05, color=COLORS["nullspace"], name="영공간", dashed=False),
]
for 벡터, 색, 이름 in [(A[0], COLORS["colspace"], "행 1"),
                     (A[1] / 1.5, COLORS["second"], "행 2"),
                     (법선, COLORS["nullspace"], "N(A) 방향")]:
    그림요소 += arrow([0, 0, 0], 벡터, 색, 이름)

spin_figure(그림요소, title="행공간(평면)과 영공간(직선)은 직각으로 만난다",
            extent=2.6, n_frames=48, radius=2.4)
Loading...

5절의 분해도 그려 보자. 주황이 x\vv{x}, 파랑이 행공간 성분, 회색이 영공간 성분이다. 평행사변형이 닫히는 것이 분해가 존재한다는 뜻이고, 닫히는 방법이 하나뿐인 것이 유일성이다.

분해요소 = [plane(normal=법선, extent=2.4, color=COLORS["colspace"],
                opacity=0.28, name="행공간")]
분해요소 += arrow([0, 0, 0], x, COLORS["output"], "x")
분해요소 += arrow([0, 0, 0], x_r, COLORS["input"], "x_r")
분해요소 += arrow([0, 0, 0], x_n, COLORS["nullspace"], "x_n")
분해요소 += arrow(x_r, x, COLORS["nullspace"], "", dashed=True, legend=False)
분해요소 += arrow(x_n, x, COLORS["input"], "", dashed=True, legend=False)

go.Figure(data=분해요소, layout=layout3d("x = x_r + x_n", extent=2.6))
Loading...

마치며...

서술 파트의 내용이 노트북의 코드
내적과 길이x @ y, np.linalg.norm(x)
피타고라스 \Leftrightarrow 내적 0피타고라스검사(x, y)
바닥과 벽내적표(바닥, 벽) 에 1 이 남는다
행공간 \perp 영공간A @ null_space(A) 가 영행렬
열공간 \perp 좌영공간직교확인(A)
유일한 분해쪼개기(A, x)
N(ATA)=N(A)\Nul(A^{\mathsf{T}}A) = \Nul(A)무작위 행렬로 랭크 대조
ATAA^{\mathsf{T}}A 가역 조건열이 독립인 줄만 가역 이 참

더 해 볼 것

  1. 쪼개기 는 두 기저를 붙여 연립방정식을 푼다. 3절에서 본 대로 영공간 기저는 정규직교이니, 훨씬 짧은 식으로 xn\vv{x}_n 을 구할 수 있다. 어떤 식일지 짐작해 보자. 다음 강의에서 그 식이 나온다.

  2. 직교확인 에 세 번째 쌍 “행공간 . 열공간” 을 넣어 보자. 0이 나오는가? 왜 그런가, 혹은 왜 그렇지 않은가?

  3. 7절의 그림에서 법선 을 바꿔 가며 평면과 직선이 함께 돌아가는지 보자.

  4. ATAA^{\mathsf{T}}A 대신 AATAA^{\mathsf{T}} 를 보자. 랭크는 얼마인가? 영공간은 AA 의 어느 부분공간과 같은가?

다음 강의에서는 xr\vv{x}_r 을 실제로 구하는 방법을 배운다. 그리고 같은 방법이 b\vv{b} 가 열공간 밖에 있어 해가 없을 때 무엇을 할 것인가에도 답한다.