확률 과정 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

# 확률 과정 문서의 주장을 확인한다. 실행: python3 12_random-process_verify.py (표준 라이브러리만)
# 주장 1: 문서 예의 값 읽기 X_3(w1) = 130, X_2(w2) = 300, X_1(w2) = 200
# 주장 2: 두 과정 A(매번 독립인 동전), B(어제 값을 0.9 확률로 그대로 따르는 연쇄)는
#         한 시점만 보면 둘 다 1이 절반이지만, 같은 값이 이어지는 길이의 평균은 2와 10이다.
# 주장 3: 길이 n인 0/1 수열은 2^n가지다.
# 카드 C3: 연쇄 B에서 P(X3 = 1 | X1 = 1) = 0.82.
import random
from fractions import Fraction as F


def run_lengths(seq):
    out, cur = [], 1
    for a, b in zip(seq, seq[1:]):
        if a == b:
            cur += 1
        else:
            out.append(cur)
            cur = 1
    return out                      # 마지막 미완성 구간은 뺀다


def simulate(stay, n, rng):
    """앞 값과 같을 확률이 stay인 0/1 과정. stay = 0.5면 독립인 동전과 같다."""
    x = [rng.randrange(2)]
    for _ in range(n - 1):
        x.append(x[-1] if rng.random() < stay else 1 - x[-1])
    return x


def main():
    w1 = (100, 150, 130)
    w2 = (200, 300, 400)
    X = lambda i, w: w[i - 1]       # X_i(w): 결과 w의 i번째 값 (1부터 센다)
    assert X(3, w1) == 130 and X(2, w2) == 300 and X(1, w2) == 200
    print("[OK] 주장 1: X_3(w1) = 130, X_2(w2) = 300, X_1(w2) = 200")

    # 정확한 값: 정상 분포와 평균 이어짐 길이
    for stay, mean_run in ((F(1, 2), 2), (F(9, 10), 10)):
        pi1 = F(1, 2)                                   # 대칭이라 1의 비율은 1/2
        assert pi1 * stay + (1 - pi1) * (1 - stay) == pi1   # 한 걸음 가도 그대로
        assert 1 / (1 - stay) == mean_run               # 기하분포의 평균 1/(1 - stay)
    rng = random.Random(12)
    n = 400000
    for stay, mean_run in ((0.5, 2), (0.9, 10)):
        x = simulate(stay, n, rng)
        frac = sum(x) / n
        runs = run_lengths(x)
        m = sum(runs) / len(runs)
        assert abs(frac - 0.5) < 0.01, frac
        assert abs(m - mean_run) < 0.03 * mean_run, m
        print(f"[OK] 주장 2: stay={stay} → 1의 비율 {frac:.3f}, 평균 이어짐 길이 {m:.2f}")

    # 예시: 독립이면 P(X2 = 1 | X1 = 1) = 0.5, 연쇄 B면 0.9
    x = simulate(0.9, n, rng)
    after1 = [b for a, b in zip(x, x[1:]) if a == 1]
    assert abs(sum(after1) / len(after1) - 0.9) < 0.01
    x = simulate(0.5, n, rng)
    after1 = [b for a, b in zip(x, x[1:]) if a == 1]
    assert abs(sum(after1) / len(after1) - 0.5) < 0.01
    print("[OK] 예시: 1 다음에 1이 올 비율 0.9(연쇄)와 0.5(독립)")

    # 본문의 주의 문단·카드 C3: 어제에만 달려 있는 연쇄 B에서 오늘은 그저께와 독립이 아니다
    stay = F(9, 10)
    p_two_steps = stay * stay + (1 - stay) * (1 - stay)      # P(X3 = 1 | X1 = 1)
    assert p_two_steps == F(41, 50) and p_two_steps != F(1, 2)
    # 어제를 알고 나면 그저께는 더 알려 주는 것이 없다: P(X3 = 1 | X2 = 1, X1 = a)는 a와 무관하게 0.9
    x = simulate(0.9, n, rng)
    for a in (0, 1):
        nxt = [x[i + 2] for i in range(n - 2) if x[i] == a and x[i + 1] == 1]
        assert abs(sum(nxt) / len(nxt) - 0.9) < 0.01
    after = [x[i + 2] for i in range(n - 2) if x[i] == 1]
    assert abs(sum(after) / len(after) - 0.82) < 0.01
    print("[OK] 카드 C3: P(X3 = 1 | X1 = 1) = 0.82 (0.5가 아니다), 어제를 알면 그저께와 무관하게 0.9")

    assert [2 ** k for k in (3, 10)] == [8, 1024]
    print("[OK] 주장 3: 길이 3이면 8가지, 길이 10이면 1024가지")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()