그래디언트와 방향도함수 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""그래디언트와 방향도함수 검증.

문서: 20.그래디언트와 방향도함수 (예시 표, 정의, 정리, 증명, 가정, 예제, 활용, 오해, 카드 C1~C4)
주장 1: 예시 — f = 10 - x² - 2y², (1,1): ∇f = (-2,-4), 방향도함수 -2, -4, √20, 0 (정의의 극한을 작은 h로).
주장 2: 무작위 매끄러운 함수·점·단위벡터 — 정의의 차분 = ∇f·u.
주장 3: 무작위 방향 2000개 중 방향도함수의 최댓값이 ||∇f||에 가깝고 넘지 않음, 최대 방향이 ∇f 방향.
주장 4: 등고선 x² + 2y² = 3 위 곡선 c(t)에서 ∇f·c'(t) = 0.
주장 5: 가정·오해·카드 C3 — x²y/(x²+y²): 원점 편미분 0, 대각 방향도함수 1/(2√2).
주장 6: 예제 — i = 3x + 4y: ∇i = (3,4), 크기 5, 등고선 방향 (4,-3) 수직. 카드 C1 — x²y의 (1,2): (4,1), 3.2.
"""
import math
import random


def grad(f, p, h=1e-6):
    out = []
    for i in range(len(p)):
        a, b = list(p), list(p)
        a[i] += h
        b[i] -= h
        out.append((f(*a) - f(*b)) / (2 * h))
    return out


def ddir(f, p, u, h=1e-6):
    a = [pi + h * ui for pi, ui in zip(p, u)]
    b = [pi - h * ui for pi, ui in zip(p, u)]
    return (f(*a) - f(*b)) / (2 * h)


def main():
    f = lambda x, y: 10 - x * x - 2 * y * y
    g = grad(f, (1, 1))
    assert abs(g[0] + 2) < 1e-8 and abs(g[1] + 4) < 1e-8
    s5 = math.sqrt(5)
    vals = [ddir(f, (1, 1), u) for u in ((1, 0), (0, 1), (-1 / s5, -2 / s5), (2 / s5, -1 / s5))]
    assert all(abs(a - b) < 1e-7 for a, b in zip(vals, [-2, -4, math.sqrt(20), 0]))
    assert abs(math.sqrt(20) - 4.47) < 0.01
    print("[OK] 주장 1: 예시 표")

    rng = random.Random(20)
    funcs = [lambda x, y: math.sin(x) * math.exp(y), lambda x, y: x ** 3 * y - y ** 2, lambda x, y: math.log(1 + x * x + y * y)]
    for F in funcs:
        for _ in range(200):
            p = (rng.uniform(-1.5, 1.5), rng.uniform(-1.5, 1.5))
            th = rng.uniform(0, 2 * math.pi)
            u = (math.cos(th), math.sin(th))
            gr = grad(F, p)
            assert abs(ddir(F, p, u) - (gr[0] * u[0] + gr[1] * u[1])) < 1e-6
    print("[OK] 주장 2·카드 C2 바탕: D_u f = ∇f·u")

    for F in funcs:
        p = (rng.uniform(-1, 1), rng.uniform(-1, 1))
        gr = grad(F, p)
        n = math.hypot(*gr)
        best, bu = -1e9, None
        for _ in range(2000):
            th = rng.uniform(0, 2 * math.pi)
            u = (math.cos(th), math.sin(th))
            d = ddir(F, p, u)
            if d > best:
                best, bu = d, u
        assert best <= n + 1e-6 and best > n - 1e-3
        assert (bu[0] * gr[0] + bu[1] * gr[1]) / n > 0.999
    print("[OK] 주장 3: 가장 가파른 방향")

    for _ in range(200):
        t = rng.uniform(0, 2 * math.pi)
        c = (math.sqrt(3) * math.cos(t), math.sqrt(1.5) * math.sin(t))
        dc = (-math.sqrt(3) * math.sin(t), math.sqrt(1.5) * math.cos(t))
        assert abs(f(*c) - 7) < 1e-12
        gr = grad(f, c)
        assert abs(gr[0] * dc[0] + gr[1] * dc[1]) < 1e-6
    print("[OK] 주장 4·카드 C4: 등고선과 수직")

    h = lambda x, y: x * x * y / (x * x + y * y) if (x, y) != (0, 0) else 0.0
    for t in (1e-3, 1e-6):
        assert h(t, 0) == 0 and h(0, t) == 0
    u = (1 / math.sqrt(2), 1 / math.sqrt(2))
    for t in (1e-2, 1e-5):
        assert abs(h(t * u[0], t * u[1]) / t - 1 / (2 * math.sqrt(2))) < 1e-12
    assert abs(1 / (2 * math.sqrt(2)) - 0.354) < 0.001
    print("[OK] 주장 5·오해·카드 C3: 미분 가능성이 필요")

    I = lambda x, y: 3 * x + 4 * y
    gi = grad(I, (0.3, -0.7))
    assert abs(gi[0] - 3) < 1e-8 and abs(gi[1] - 4) < 1e-8 and abs(math.hypot(*gi) - 5) < 1e-8
    assert 3 * 4 + 4 * -3 == 0
    q = lambda x, y: x * x * y
    gq = grad(q, (1, 2))
    assert abs(gq[0] - 4) < 1e-8 and abs(gq[1] - 1) < 1e-8 and abs(ddir(q, (1, 2), (0.6, 0.8)) - 3.2) < 1e-8
    print("[OK] 주장 6·예제·카드 C1")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()