정적분과 리만 합 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""정적분과 리만 합 검증.

문서: 11.정적분과 리만 합 (예시, 정의, 증명, 예제, 활용, 오해, 카드 C1~C4)
주장 1: v(t) = t²의 [0, 3] 왼쪽·오른쪽 끝 합 표(n = 3, 6, 30, 300)와 극한 9. 닫힌 꼴 R_n = 9(n+1)(2n+1)/(2n²).
주장 2: 예제 ∫_0^1 x dx의 R_n = (n+1)/(2n) -> 1/2 (유리수로 정확히).
주장 3: 카드 C2: x²의 [0, 2] 4등분 합 1.75, 3.75, 참값 8/3, 차 2.
주장 4: 증가함수에서 R_n - L_n = (f(b) - f(a))(b - a)/n (무작위 증가함수 200개, 유리수로 정확히).
주장 5: 계단 함수 floor x의 [0, 3] 적분 3, 리만 합이 3으로 모임.
주장 6: 디리클레 함수는 유리수 점을 고르면 합이 b - a, 무리수 점을 고르면 0 (구성으로).
주장 7: 오해: ∫_0^{2π} sin = 0, ∫_0^{2π} |sin| = 4 (심프슨).
주장 8: 오차 차수(실험): n을 두 배로 하면 왼쪽 끝 오차 약 1/2, 중점·사다리꼴 약 1/4, 심프슨 약 1/16.
"""
import math
import random
from fractions import Fraction as F


def riemann(f, a, b, n, shift):
    dx = (b - a) / n
    return sum(f(a + (i + shift) * dx) for i in range(n)) * dx


def simpson(f, a, b, n):
    dx = (b - a) / n
    s = f(a) + f(b) + 4 * sum(f(a + i * dx) for i in range(1, n, 2)) + 2 * sum(f(a + i * dx) for i in range(2, n, 2))
    return s * dx / 3


def main():
    sq = lambda x: x * x
    table = {3: (5, 14), 6: (6.875, 11.375), 30: (8.555, 9.455), 300: (8.95505, 9.04505)}
    for n, (L, R) in table.items():
        Le, Re = riemann(lambda x: x * x, F(0), F(3), n, 0), riemann(lambda x: x * x, F(0), F(3), n, 1)
        assert Re == F(9 * (n + 1) * (2 * n + 1), 2 * n * n) and Le == F(9 * (n - 1) * (2 * n - 1), 2 * n * n)
        assert abs(float(Le) - L) < 1e-9 and abs(float(Re) - R) < 1e-9
    assert abs(float(riemann(sq, F(0), F(3), 10 ** 4, 1)) - 9) < 2e-3
    print("[OK] 주장 1: 예시 표와 극한 9")

    for n in (1, 2, 10, 1000):
        assert riemann(lambda x: x, F(0), F(1), n, 1) == F(n + 1, 2 * n)
    print("[OK] 주장 2: 예제 (n+1)/(2n)")

    L, R = riemann(sq, F(0), F(2), 4, 0), riemann(sq, F(0), F(2), 4, 1)
    assert (L, R) == (F(7, 4), F(15, 4)) and L < F(8, 3) < R and R - L == 2
    print("[OK] 주장 3·카드 C2")

    rng = random.Random(11)
    for _ in range(200):
        steps = [F(rng.randint(0, 9), rng.randint(1, 5)) for _ in range(6)]
        cum = [sum(steps[:k + 1]) for k in range(6)]  # 증가하는 조각 값
        a, b = F(0), F(rng.randint(1, 5))
        f = lambda x, a=a, b=b, cum=cum: cum[min(5, int((x - a) / (b - a) * 6))] + x
        n = rng.choice([6, 12, 30])
        diff = riemann(f, a, b, n, 1) - riemann(f, a, b, n, 0)
        assert diff == (f(b) - f(a)) * (b - a) / n
    print("[OK] 주장 4·카드 C4: R_n - L_n = (f(b) - f(a))Δx")

    fl = lambda x: math.floor(x)
    assert abs(riemann(fl, 0, 3, 3000, 0.5) - 3) < 1e-9
    print("[OK] 주장 5: 계단 함수 3")

    for n in (1, 7, 100):
        a, b = F(0), F(1)
        dx = (b - a) / n
        rational_pts = [a + i * dx for i in range(n)]              # 유리수 -> 값 1
        irr_pts = [(a + i * dx, dx / 2) for i in range(n)]          # a + i·dx + (dx/2)·√2: 무리수 -> 값 0
        S_rat = sum(1 * dx for _ in rational_pts)
        S_irr = sum(0 * dx for _ in irr_pts)
        assert S_rat == b - a and S_irr == 0
    print("[OK] 주장 6·카드 C3: 디리클레 함수의 합은 1과 0으로 갈린다")

    assert abs(simpson(math.sin, 0, 2 * math.pi, 1000)) < 1e-12
    assert abs(simpson(lambda x: abs(math.sin(x)), 0, 2 * math.pi, 1000) - 4) < 1e-9
    assert abs(simpson(math.sin, 0, math.pi, 100) - 2) < 1e-7
    print("[OK] 주장 7·오해: sin의 0과 |sin|의 4")

    ex = math.e - 1
    ratio = lambda g: abs(g(64) - ex) / abs(g(128) - ex)
    rl = ratio(lambda n: riemann(math.exp, 0, 1, n, 0))
    rm = ratio(lambda n: riemann(math.exp, 0, 1, n, 0.5))
    dxs = lambda n: 1 / n
    rt = ratio(lambda n: dxs(n) * (0.5 * math.exp(0) + sum(math.exp(i / n) for i in range(1, n)) + 0.5 * math.e))
    rs = abs(simpson(math.exp, 0, 1, 16) - ex) / abs(simpson(math.exp, 0, 1, 32) - ex)
    assert 1.9 < rl < 2.1 and 3.9 < rm < 4.1 and 3.9 < rt < 4.1 and 15 < rs < 17
    print(f"[OK] 주장 8: 오차 비 왼쪽 {rl:.2f}, 중점 {rm:.2f}, 사다리꼴 {rt:.2f}, 심프슨 {rs:.2f}")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()