행렬과 행렬-벡터 곱 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""행렬과 행렬-벡터 곱 검증.

문서: 04.행렬과 행렬-벡터 곱 (예시, 정의, 선형성, 정리, 예제, 활용, 오해, 카드 C1~C4)
주장 1: 예시 — [[2,1],[1,3]]·(4,5) = (13, 19), 열 관점과 행 관점이 같다.
주장 2: 무작위 행렬 1000개에서 열 관점 = 행 관점, 선형성 A(cx + dy) = cAx + dAy (유리수 정확).
주장 3: 선형 함수(무작위 행렬로 만든 T)는 열이 T(e_j)인 행렬과 같다. A e_j = a_j.
주장 4: 예제 — Strang 행렬 A·(2, 1, -2) = (2, 12, 2).
주장 5: 오해·카드 C4 — 평행이동은 T(0) ≠ 0, T(2e1) ≠ 2T(e1). 동차 좌표 3×3 행렬로 평행이동.
주장 6: 카드 C1 — (0, 2, 4). 카드 C2의 행렬로 (x, y, z) = 임의 값에서 원래 식과 같다. 카드 C3 — T(x, y) = xT(e1) + yT(e2).
"""
import random
from fractions import Fraction as F


def col_view(A, x):
    m = len(A)
    out = [0] * m
    for j, xj in enumerate(x):
        for i in range(m):
            out[i] += xj * A[i][j]
    return tuple(out)


def row_view(A, x):
    return tuple(sum(a * b for a, b in zip(row, x)) for row in A)


def main():
    A = [[2, 1], [1, 3]]
    assert col_view(A, (4, 5)) == row_view(A, (4, 5)) == (13, 19)
    print("[OK] 주장 1: 예시")

    rng = random.Random(4)
    for _ in range(1000):
        m, n = rng.randint(1, 5), rng.randint(1, 5)
        A = [[F(rng.randint(-9, 9), rng.randint(1, 3)) for _ in range(n)] for _ in range(m)]
        x = [F(rng.randint(-9, 9)) for _ in range(n)]
        y = [F(rng.randint(-9, 9), 2) for _ in range(n)]
        c, d = F(rng.randint(-5, 5)), F(rng.randint(-5, 5), 3)
        assert col_view(A, x) == row_view(A, x)
        lhs = row_view(A, [c * p + d * q for p, q in zip(x, y)])
        rhs = tuple(c * p + d * q for p, q in zip(row_view(A, x), row_view(A, y)))
        assert lhs == rhs
        E = [[F(int(i == j)) for i in range(n)] for j in range(n)]
        T = lambda v, A=A: row_view(A, v)
        B = [[T(E[j])[i] for j in range(n)] for i in range(m)]
        assert B == A and all(T(E[j]) == tuple(A[i][j] for i in range(m)) for j in range(n))
        assert T(x) == row_view(B, x)
    print("[OK] 주장 2·3: 두 관점, 선형성, 선형 함수 = 행렬")

    S = [[1, 2, 1], [3, 8, 1], [0, 4, 1]]
    assert row_view(S, (2, 1, -2)) == (2, 12, 2)
    print("[OK] 주장 4: 예제")

    T = lambda v: (v[0] + 1, v[1])
    assert T((0, 0)) != (0, 0) and T((2, 0)) == (3, 0) and tuple(2 * p for p in T((1, 0))) == (4, 0)
    H = [[1, 0, 1], [0, 1, 0], [0, 0, 1]]
    for _ in range(100):
        v = (rng.randint(-9, 9), rng.randint(-9, 9))
        assert row_view(H, (v[0], v[1], 1)) == (v[0] + 1, v[1], 1)
    print("[OK] 주장 5·오해·카드 C4: 평행이동과 동차 좌표")

    C1 = [[1, 2], [3, 4], [5, 6]]
    assert col_view(C1, (2, -1)) == (0, 2, 4)
    C2 = [[1, -1, 0], [2, 0, 1], [0, 1, 3]]
    for _ in range(100):
        x, y, z = (rng.randint(-9, 9) for _ in range(3))
        assert row_view(C2, (x, y, z)) == (x - y, 2 * x + z, y + 3 * z)
    C3 = [[2, -1], [1, 3]]
    for _ in range(100):
        x, y = rng.randint(-9, 9), rng.randint(-9, 9)
        assert row_view(C3, (x, y)) == tuple(x * p + y * q for p, q in zip((2, 1), (-1, 3)))
    print("[OK] 주장 6·카드 C1·C2·C3")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()