---
jupytext:
  formats: md:myst
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
kernelspec:
  display_name: Python 3
  language: python
  name: python3
---

# 8회 실험 · 리만 조건

```{admonition} 이 실험
:class: seealso

| | |
|---|---|
| **짝 서술** | {doc}`L08` |
| **실험 목록** | 실험 1 (1층) — $\epsilon$이 먼저, $n$이 나중 / 실험 2 (3층) — 필요한 $n$을 식으로 푼다 |
```

{prf:ref}`thm-8-riemann`의 조건은 "$\epsilon$이 주어지면 계단함수 쌍이 존재한다"는 형태이다. 이 실험의 목적은 **코드에서 무엇이 먼저 정해지는지**를 눈으로 확인하는 것이다.

```{code-cell} ipython3
:tags: [hide-input]

# 공통 준비 — 색 규약과 한글 글꼴을 맞춘다
import numpy as np
import sympy as sp

from calc_viz import 상합하합, 칸
from calc_style import 한글글꼴설정

글꼴 = 한글글꼴설정()

f = lambda x: x**2
```

## 실험 1 (1층) — $\epsilon$이 먼저, $n$이 나중

함수 `필요한분할수`의 인자를 보면 `eps`가 입력이고 `n`이 출력이다. **$\epsilon$을 받아서 $n$을 돌려준다.** 이 방향이 리만 조건의 양화사 순서와 같다.

```{code-cell} ipython3
# [8회] 리만 조건
# 목적 : 허용 오차가 먼저 주어지고 그다음 분할을 찾는다는 순서를 확인한다
# 층  : 1층
# 주의 : eps 가 인자이고 n 이 반환값이다. 순서가 뒤바뀌면 다른 명제가 된다

def 필요한분할수(f, a, b, eps, 최대=10**6):
    """U_n − L_n < eps 를 만족하는 n 을 하나 찾는다. 최선의 n 을 찾지는 않는다."""
    n_ = 1
    while n_ <= 최대:
        하, 상 = 상합하합(f, a, b, n_)
        if 상 - 하 < eps:
            return n_, 상 - 하
        n_ *= 2
    return None, None

머리 = ["eps", "찾은 n", "그때의 U_n − L_n", "eps 보다 작은가"]
폭 = [12, 10, 22, 16]
print(" ".join(칸(글, w) for 글, w in zip(머리, 폭)))

for eps in (0.1, 0.01, 0.001, 0.0001):
    n_, 차이 = 필요한분할수(f, 0.0, 1.0, eps)
    줄 = [eps, n_, f"{차이:.10f}", 차이 < eps]
    print(" ".join(칸(항목, w) for 항목, w in zip(줄, 폭)))
```

$\epsilon$이 작아지면 필요한 $n$이 커진다. 하나의 $n$이 모든 $\epsilon$에 대하여 통하지 않는다는 것이 이 표가 보여 주는 것이다.

```{admonition} 확인할 것
:class: tip
1. `필요한분할수`의 인자와 반환값을 보자. $\epsilon$과 $n$ 중 무엇이 먼저 정해지는가.
2. $\epsilon$을 10배 줄이면 찾은 $n$이 몇 배가 되는가.
3. 이 함수가 돌려주는 $n$이 조건을 만족하는 **가장 작은** $n$인가.
```

## 실험 2 (3층) — 필요한 $n$을 식으로 푼다

실험 1은 $n$을 2배씩 늘려 가며 찾으므로 실제로 필요한 최소 $n$을 주지 못한다. 기호로 풀면 정확한 경계가 나온다.

```{code-cell} ipython3
# [8회] 리만 조건을 만족하는 n 의 정확한 경계
# 목적 : eps 가 주어졌을 때 필요한 n 을 식으로 구한다
# 층  : 3층
# 주의 : 부등호가 < 이지 ≤ 가 아니다. 경계값 자체는 조건을 만족하지 않는다

from calc_sym import 균등분할_상합하합, x, n, b as b기호, eps as eps기호

하합, 상합 = 균등분할_상합하합(x**2, x, 0, b기호, 증가함수=True)
차이 = sp.simplify(상합 - 하합)
print("U_n − L_n ="); sp.pprint(차이)

조건 = sp.simplify(차이.subs(b기호, 1)) < eps기호
print(f"\n조건 : {조건}")
print(f"풀면 : {sp.solve(조건, n)}")
```

$1/n < \epsilon$의 해는 $n > 1/\epsilon$이다. 부등호가 **엄격하므로** 최소인 자연수는 $1/\epsilon$의 올림값이 아니라

$$
n_{\min} = \left\lfloor \frac{1}{\epsilon} \right\rfloor + 1
$$

이다. $\epsilon = 0.1$이면 $1/\epsilon = 10$인데 $1/10 = 0.1$은 $0.1$보다 작지 않으므로 $n=10$은 조건을 만족하지 않는다. $n=11$이 최소이다.

```{code-cell} ipython3
# [8회] 정확한 최소 n 과 실험 1 이 찾은 n 의 대조
# 목적 : 2배씩 늘리는 탐색이 최소값을 넘어선다는 것을 확인한다
# 층  : 3층과 1층의 대조
# 주의 : Rational(str(e)) 로 두어 부동소수 오차 없이 계산한다

머리 = ["eps", "정확한 최소 n", "실험 1 의 n", "1/n_min < eps 인가"]
폭 = [12, 16, 14, 20]
print(" ".join(칸(글, w) for 글, w in zip(머리, 폭)))

for e in (0.1, 0.01, 0.001, 0.0001):
    er = sp.Rational(str(e))
    최소n = int(sp.floor(1 / er) + 1)
    n1, _ = 필요한분할수(f, 0.0, 1.0, e)
    줄 = [e, 최소n, n1, bool(sp.Rational(1, 최소n) < er)]
    print(" ".join(칸(항목, w) for 항목, w in zip(줄, 폭)))
```

실험 1이 찾은 $n$이 언제나 최소값보다 크다. $1$에서 시작해 2배씩 늘리므로 조건을 처음 만족하는 2의 거듭제곱에서 멈추기 때문이다.

**이것은 문제가 되지 않는다.** {prf:ref}`thm-8-riemann`은 조건을 만족하는 계단함수 쌍이 **존재한다**고만 요구한다. 최선의 쌍을 찾을 필요가 없다.

```{admonition} 확인할 것
:class: tip
1. 실험 2에서 필요한 최소 $n$이 $1/\epsilon$의 올림값인지 확인하자. 아니라면 왜 그런가.
2. 실험 1이 찾은 $n$이 실험 2의 최소값보다 큰지 확인하자. 왜 그런가.
3. 코드에서 $\epsilon$과 $n$ 중 무엇이 먼저 정해지는지 확인하자.
```

```{admonition} 첫 번째 항목에 유의한다
:class: warning

$\epsilon = 0.1$일 때 $1/\epsilon$의 올림값은 $10$이지만, $1/10 = 0.1$은 $0.1$보다 **작지 않다.** 리만 조건의 부등호는 엄격한 부등호이므로 $n=10$은 조건을 만족하지 못한다.

경계에서 등호가 어느 쪽에 붙는지를 확인하는 것은 이후에도 계속 필요하다. 부등호 하나가 답을 하나 어긋나게 한다.
```

```{admonition} 짝이 되는 서술
:class: note
{doc}`L08`
```
