10회 실험 · 단조함수와 반례#

이 실험

짝 서술

10회 · 적분 가능한 함수

확인하는 것

단조함수에서 진동의 합이 고정된다는 것과, 그 합이 고정되지 않는 함수가 있다는 것

서술 파트의 (10.5)는 단조함수의 상합과 하합의 차이를 식 하나로 준다. 분자에 \(n\)이 없다는 것이 이 식의 전부이다.

이 노트북에서는 그 식을 네 개의 함수에서 확인한다. 수치로 한 번, sympy의 기호 계산으로 한 번 확인한다. 기호 계산 쪽에서는 망원합이 실제로 상쇄되는 것이 눈에 보인다.

그다음 (10.4)의 각 항이 직사각형 하나가 되는 그림을 그린다. 상합에서 하합을 뺀 띠들을 옆으로 밀어 쌓으면 폭 \((b-a)/n\), 높이 \(f(b)-f(a)\)인 직사각형 하나가 된다는 것을 코드가 그대로 수행한다.

마지막으로 디리클레 함수를 다룬다. 표본으로 그리면 무엇이 보이는지, (10.11)의 진동의 합이 어떻게 다른지, 그리고 수치 적분 라이브러리에 넣으면 무슨 일이 일어나는지 본다.

서술 파트의 내용

여기서 확인하는 방법

(10.2) — 단조성이 최댓값과 최솟값의 위치를 정한다

표본추출 없이 끝점만으로 상합과 하합을 만든다

(10.5)의 차이 공식

증가 셋과 감소 하나, 네 함수에서 대조한다

(10.4)의 망원합

sympy로 합을 직접 계산하여 공식과 같은지 본다

차이가 직사각형 하나가 된다는 설명

띠를 잘라 한 세로줄에 쌓는 그림을 그린다

\(\epsilon\)에서 \(n\)을 정하는 계산

(10.5)를 뒤집어 최소 \(n\)을 정확한 유리수로 구한다

예제 5

표본 그림과 (10.11)의 진동의 합

적분값이 없는데 라이브러리가 값을 준다는 것

quad에 넣어 돌아오는 값을 본다

0. 준비#

이 회차에서 쓸 것만 불러온다. 색은 상수로 고정한다.

Hide code cell source

# Setup: imports, plotting defaults, and the colors used in this lecture
import numpy as np
import matplotlib.pyplot as plt
import sympy as sp
from fractions import Fraction
from scipy.integrate import quad

plt.rcParams["figure.figsize"] = (11.0, 4.2)
plt.rcParams["figure.dpi"] = 110
plt.rcParams["axes.grid"] = True
plt.rcParams["grid.alpha"] = 0.3
plt.rcParams["font.size"] = 11

C_FUNC = "#000000"
C_LOWER = "#1f77b4"
C_UPPER = "#ff7f0e"
C_DIFF = "#d62728"
C_CEX = "#9467bd"

1. 단조함수는 끝점만 보면 된다#

8회와 9회의 실험에서는 각 부분구간을 촘촘히 표본추출하여 최댓값과 최솟값을 추정하였다. 단조함수에서는 그럴 필요가 없다. (10.2)가 최댓값과 최솟값의 위치를 정해 주기 때문이다.

증가함수라면 \(k\)번째 부분구간의 최솟값이 \(f(x_{k-1})\)이고 최댓값이 \(f(x_k)\)이다. 감소함수라면 둘이 자리를 바꾼다.

# For a monotonic function the two endpoints already give the min and the max
def partition(a, b, n):
    """Return the n + 1 division points of the equal partition of [a, b]."""
    return np.linspace(a, b, n + 1)


def step_integral(points, heights):
    """Integral of the step function that takes heights[k] on the k-th subinterval."""
    widths = np.diff(points)
    return float(np.sum(np.asarray(heights) * widths))


def monotone_sums(f, a, b, n, increasing=True):
    """Return (lower, upper) for a monotonic f on the equal partition of [a, b].

    No sampling is used. Formula (10.2) says a monotonic f attains its minimum
    and its maximum on each subinterval at the two endpoints.
    """
    points = partition(a, b, n)
    at_left = f(points[:-1])
    at_right = f(points[1:])
    if increasing:
        return step_integral(points, at_left), step_integral(points, at_right)
    return step_integral(points, at_right), step_integral(points, at_left)

네 함수에서 (10.5)를 확인한다. 셋은 증가하고 하나는 감소한다.

감소하는 경우에는 \(f(b) - f(a)\)가 음수이므로 \(U_n - L_n\)은 그 절댓값이다. (10.5)의 유도에서 증가하는 경우를 다루었기 때문이며, 감소하는 경우에는 \(f(a) - f(b)\)가 그 자리에 온다.

# Check (10.5) on three increasing functions and one decreasing function
cases = (
    ("x**2      on [0, 1]", lambda t: t ** 2, 0.0, 1.0, True),
    ("x**3      on [0, 2]", lambda t: t ** 3, 0.0, 2.0, True),
    ("sqrt(x)   on [0, 4]", np.sqrt, 0.0, 4.0, True),
    ("1/(1 + x) on [0, 3]", lambda t: 1.0 / (1.0 + t), 0.0, 3.0, False),
)

header = f"{'function':>20} {'n':>6} {'U_n - L_n':>15} {'formula (10.5)':>16}"
print(header)
print("-" * len(header))
for name, fn, a, b, increasing in cases:
    ends = abs(float(fn(np.array([b]))[0]) - float(fn(np.array([a]))[0]))
    for n in (10, 100, 1000):
        lower, upper = monotone_sums(fn, a, b, n, increasing)
        print(f"{name:>20} {n:>6} {upper - lower:>15.10f} {(b - a) * ends / n:>16.10f}")
            function      n       U_n - L_n   formula (10.5)
------------------------------------------------------------
 x**2      on [0, 1]     10    0.1000000000     0.1000000000
 x**2      on [0, 1]    100    0.0100000000     0.0100000000
 x**2      on [0, 1]   1000    0.0010000000     0.0010000000
 x**3      on [0, 2]     10    1.6000000000     1.6000000000
 x**3      on [0, 2]    100    0.1600000000     0.1600000000
 x**3      on [0, 2]   1000    0.0160000000     0.0160000000
 sqrt(x)   on [0, 4]     10    0.8000000000     0.8000000000
 sqrt(x)   on [0, 4]    100    0.0800000000     0.0800000000
 sqrt(x)   on [0, 4]   1000    0.0080000000     0.0080000000
 1/(1 + x) on [0, 3]     10    0.2250000000     0.2250000000
 1/(1 + x) on [0, 3]    100    0.0225000000     0.0225000000
 1/(1 + x) on [0, 3]   1000    0.0022500000     0.0022500000

두 열이 소수점 열 자리까지 일치한다. \(n\)을 \(10\)배 하면 차이가 \(10\)분의 \(1\)이 된다. 함수의 모양과 무관하게 그렇다.

\(x^3\)을 \([0,2]\)에서 볼 때 \(n=10\)의 차이가 \(1.6\)으로 크다. 구간의 폭이 \(2\)이고 양 끝값의 차가 \(8\)이기 때문이다. 그래도 \(n\)에 반비례하여 줄어드는 것은 같다.

2. 망원합을 sympy가 대신 계산한다#

(10.4)의 합이 (10.5)로 정리되는 것은 4회 정리 10의 망원합 때문이다. 손으로 상쇄시킨 것을 sympy에 그대로 시켜 본다.

\(k\)번째 부분구간의 오른쪽 끝은 \(a + k\frac{b-a}{n}\)이고 왼쪽 끝은 거기서 폭을 뺀 것이다. 두 값에서의 함숫값 차를 \(k = 1\)부터 \(n\)까지 더한 뒤 폭을 곱한다.

# Let sympy carry out the telescoping sum of (10.4)
x, k = sp.symbols("x k")
n = sp.Symbol("n", integer=True, positive=True)


def symbolic_gap(expr, a, b):
    """(b - a)/n times the sum of (10.4), evaluated by sympy."""
    width = sp.Rational(b - a, 1) / n
    node = a + k * width
    term = sp.expand(expr.subs(x, node) - expr.subs(x, node - width))
    return sp.simplify(width * sp.summation(term, (k, 1, n)))


symbolic_cases = ((x ** 2, 0, 1), (x ** 3, 0, 2), (sp.sqrt(x), 0, 4), (1 / (1 + x), 0, 3))
for expr, a, b in symbolic_cases:
    summed = symbolic_gap(expr, a, b)
    predicted = sp.Rational(b - a, 1) * (expr.subs(x, b) - expr.subs(x, a)) / n
    print(f"{str(expr):>12} on [{a}, {b}] :  sum = {summed}"
          f"   formula = {sp.simplify(predicted)}"
          f"   equal = {sp.simplify(summed - predicted) == 0}")
        x**2 on [0, 1] :  sum = 1/n   formula = 1/n   equal = True
        x**3 on [0, 2] :  sum = 16/n   formula = 16/n   equal = True
     sqrt(x) on [0, 4] :  sum = 8/n   formula = 8/n   equal = True
   1/(x + 1) on [0, 3] :  sum = -9/(4*n)   formula = -9/(4*n)   equal = True

네 경우 모두 sympy가 계산한 합과 (10.5)가 주는 식이 같다.

주목할 것은 \(\sqrt{x}\)와 \(1/(1+x)\)이다. 이 두 함수에서는 \(\sum_{k=1}^n f(x_k)\)의 닫힌 식이 없다. 4회에서 \(\sum k\)와 \(\sum k^2\)의 공식을 얻은 것과 같은 방법을 쓸 수 없다는 뜻이다. 그런데도 차이는 \(n\)으로 나눈 상수 하나로 정리된다. 개별 합을 몰라도 차이는 알 수 있다는 것이 망원합의 힘이다.

\(1/(1+x)\)의 결과가 \(-\frac{9}{4n}\)으로 음수이다. 이 함수가 감소하므로 \(f(b)-f(a) < 0\)이기 때문이다. \(U_n - L_n\)은 그 절댓값 \(\frac{9}{4n}\)이며, 1절 표의 \(n=10\) 행 \(0.225\)와 맞는다.

3. 띠를 쌓으면 직사각형 하나가 된다#

(10.4)의 각 항은 폭 \((b-a)/n\), 높이 \(f(x_k)-f(x_{k-1})\)인 직사각형의 넓이이다. 왼쪽 그림에서 주황 띠가 그것이다. 폭이 모두 같으므로 이 띠들을 옆으로 밀어 한 세로줄에 쌓을 수 있고, 쌓인 높이가 \(f(b)-f(a)\)이다.

코드가 그 조작을 그대로 수행한다. bottoms가 각 띠를 어디에 올릴지 정하는 배열이다.

앞 절에서 n을 sympy 기호로 묶었으므로, 아래 셀에서 다시 정수로 묶인다는 점에 유의한다. 이 셀 이후로 symbolic_gap을 다시 부르려면 2절의 셀을 먼저 실행해야 한다.

# Cut the strips out of the left picture and stack them into one rectangle
def f(t):
    return t ** 2


a, b, n = 0.0, 1.0, 8
points = partition(a, b, n)
left = points[:-1]
widths = np.diff(points)
lows = f(points[:-1])
highs = f(points[1:])
strips = highs - lows
bottoms = np.concatenate(([0.0], np.cumsum(strips)[:-1]))
curve = np.linspace(a, b, 400)

fig, axes = plt.subplots(1, 2)

ax = axes[0]
ax.bar(left, highs, width=widths, align="edge",
       color=C_UPPER, alpha=0.55, edgecolor="white", linewidth=0.5, label="upper sum")
ax.bar(left, lows, width=widths, align="edge",
       color=C_LOWER, alpha=0.85, edgecolor="white", linewidth=0.5, label="lower sum")
ax.plot(curve, f(curve), color=C_FUNC, linewidth=1.8, label="f(x) = x**2")
ax.set_title(f"n = {n} :  the orange strips add up to U_n - L_n")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_ylim(0.0, 1.05)
ax.legend(loc="upper left", fontsize=9)

ax = axes[1]
ax.bar(np.zeros(n), strips, width=widths[0], bottom=bottoms, align="edge",
       color=C_DIFF, alpha=0.6, edgecolor="white", linewidth=0.8,
       label="the same strips, stacked")
ax.set_xlim(a, b)
ax.set_ylim(0.0, 1.05)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title(f"one rectangle : width {widths[0]:.4f} times height {strips.sum():.4f}")
ax.legend(loc="upper right", fontsize=9)

fig.tight_layout()
plt.show()

lower, upper = monotone_sums(f, a, b, n)
print("stacked height     :", strips.sum())
print("f(b) - f(a)        :", f(b) - f(a))
print("width times height :", widths[0] * strips.sum())
print("U_n - L_n          :", upper - lower)
../_images/196ac2d62c24106527fca2c90aaa14a1f2c7400f5c81781b4f0fd6cc65656905.png
stacked height     : 1.0
f(b) - f(a)        : 1.0
width times height : 0.125
U_n - L_n          : 0.125

오른쪽 그림의 흰 선이 원래의 띠 경계이다. 여덟 개의 띠가 그대로 쌓여 있고, 높이의 합이 \(f(1)-f(0) = 1\)이다. 넓이는 \(\frac{1}{8} \times 1 = 0.125\)이며 \(U_8 - L_8\)과 같다.

\(n\)을 키우면 이 직사각형의 높이는 그대로이고 폭만 줄어든다. 높이가 \(n\)과 무관하다는 것이 (10.5)의 분자에 \(n\)이 없는 이유이다.

4. \(\epsilon\)에서 \(n\)을 정한다#

(10.5)를 \(n\)에 대하여 풀면 필요한 분할 수가 나온다. \(C = (b-a)\bigl(f(b)-f(a)\bigr)\)라 두면 조건은 \(C/n < \epsilon\)이고 해는 \(n > C/\epsilon\)이다.

부등호가 엄격하므로 조건을 만족하는 가장 작은 자연수는 \(\lfloor C/\epsilon \rfloor + 1\)이다. 8회 실험 4절에서 확인한 것과 같은 자리이다. 경계에서 부동소수 비교가 어긋나므로 sympy의 Rational로 정확하게 계산한다.

# Turn (10.5) around: given eps, what is the smallest n that works?
def smallest_n(a, b, ends_gap, eps):
    """Smallest n with (b - a) * ends_gap / n < eps, in exact arithmetic."""
    constant = (sp.Rational(str(b)) - sp.Rational(str(a))) * sp.Rational(str(ends_gap))
    return int(sp.floor(constant / sp.Rational(str(eps))) + 1)


header = f"{'eps':>10} {'n_min':>8} {'gap at n_min':>16} {'gap at n_min - 1':>18}"
print(header)
print("-" * len(header))
for eps in (0.1, 0.01, 0.001):
    n_min = smallest_n(0.0, 1.0, 1.0, eps)
    print(f"{eps:>10} {n_min:>8} {str(sp.Rational(1, n_min)):>16}"
          f" {str(sp.Rational(1, n_min - 1)):>18}")

print()
for eps in (0.1, 0.01, 0.001):
    n_min = smallest_n(0.0, 1.0, 1.0, eps)
    exact = sp.Rational(str(eps))
    print(f"eps = {eps} :  below at n_min ({sp.Rational(1, n_min) < exact}),"
          f"  not below at n_min - 1 ({sp.Rational(1, n_min - 1) < exact})")
       eps    n_min     gap at n_min   gap at n_min - 1
-------------------------------------------------------
       0.1       11             1/11               1/10
      0.01      101            1/101              1/100
     0.001     1001           1/1001             1/1000

eps = 0.1 :  below at n_min (True),  not below at n_min - 1 (False)
eps = 0.01 :  below at n_min (True),  not below at n_min - 1 (False)
eps = 0.001 :  below at n_min (True),  not below at n_min - 1 (False)

\(\epsilon = 0.1\)에서 \(n_{\min} = 11\)이다. \(n = 10\)에서는 차이가 정확히 \(0.1\)이므로 \(\epsilon\)보다 작지 않다. 부등호 하나가 답을 하나 어긋나게 하는 것이 8회와 같다.

여기서 \(\epsilon\)이 먼저 주어지고 \(n\)이 뒤에 정해진다. 정리 25의 양화사 순서가 코드의 인자와 반환값으로 나타난 것이다.

5. 그릴 수 없는 함수#

이제 예제 5을 본다. 이 함수를 표본으로 그리면 무엇이 보이는지 확인한다.

유리수 쪽은 분모가 \(40\) 이하인 기약분수를 모아 찍고, 무리수 쪽은 \(\sqrt{2}\)의 정수배에서 정수 부분을 뺀 값을 찍는다. 두 번째가 무리수인 이유는 \(\sqrt{2}\,j\)가 무리수이고 거기서 정수를 빼도 무리수이기 때문이다.

# What a finite sample of the Dirichlet function looks like
fig, ax = plt.subplots(figsize=(11.0, 3.2))

rationals = np.array([j / m for m in range(1, 41) for j in range(0, m + 1)])
irrationals = (np.sqrt(2.0) * np.arange(1, 601)) % 1.0

ax.scatter(rationals, np.ones_like(rationals), s=7, color=C_LOWER, label="rational -> 1")
ax.scatter(irrationals, np.zeros_like(irrationals), s=7, color=C_CEX, label="irrational -> 0")
ax.set_xlim(0.0, 1.0)
ax.set_ylim(-0.35, 1.35)
ax.set_xlabel("x")
ax.set_ylabel("f(x)")
ax.set_title("a finite sample of the Dirichlet function")
ax.legend(loc="center right", fontsize=9)

fig.tight_layout()
plt.show()

print("rational points drawn   :", rationals.size)
print("irrational points drawn :", irrationals.size)
../_images/5e71104a4612c2378ba2bb1e46b3111bb3831b748f4ef46dcdf9c9aa0817874b.png
rational points drawn   : 860
irrational points drawn : 600

두 개의 수평선처럼 보인다. 그러나 그것은 그림이지 그래프가 아니다.

위쪽 선에 빈틈이 보이는 것은 분모를 \(40\)까지만 잡았기 때문이다. 분모를 키우면 빈틈이 메워진다. 그리고 두 선 사이가 비어 있는 것처럼 보이지만, 이 함수의 값은 \(0\)과 \(1\)뿐이므로 그 사이에는 원래 아무것도 없다.

정작 그림이 감추는 것은 다른 데 있다. 어떤 짧은 구간을 확대해도 위쪽 점과 아래쪽 점이 함께 있다. 보조정리 4가 말하는 것이 그것이며, 화면의 해상도로는 확인할 수 없다.

6. 진동의 합이 갈린다#

(10.11)은 \(U_n - L_n\)을 진동의 합으로 다시 쓴 것이다. 단조함수와 디리클레 함수를 이 식에 나란히 넣는다.

단조함수의 진동은 끝점의 차이므로 배열의 차분으로 구한다. 디리클레 함수는 모든 부분구간에서 진동이 \(1\)이므로 합이 곧 \(n\)이다.

# The oscillation sum of (10.11) for a monotonic function and for the Dirichlet function
def oscillation_sum_monotone(f, a, b, n):
    """Sum of the oscillations of a monotonic f over the equal partition."""
    return float(np.sum(np.abs(np.diff(f(partition(a, b, n))))))


header = (f"{'n':>6} {'sum for x**2':>14} {'U_n - L_n':>13}"
          f" {'sum for Dirichlet':>19} {'U_n - L_n':>13}")
print(header)
print("-" * len(header))
for n in (1, 10, 100, 1000):
    monotone_total = oscillation_sum_monotone(f, 0.0, 1.0, n)
    dirichlet_total = float(n)
    print(f"{n:>6} {monotone_total:>14.6f} {monotone_total / n:>13.6f}"
          f" {dirichlet_total:>19.6f} {dirichlet_total / n:>13.6f}")
     n   sum for x**2     U_n - L_n   sum for Dirichlet     U_n - L_n
---------------------------------------------------------------------
     1       1.000000      1.000000            1.000000      1.000000
    10       1.000000      0.100000           10.000000      1.000000
   100       1.000000      0.010000          100.000000      1.000000
  1000       1.000000      0.001000         1000.000000      1.000000

\(x^2\)의 진동의 합은 \(n\)이 무엇이든 \(1\)이다. 망원합이므로 그렇다. \(1/n\)을 곱하면 \(U_n - L_n\)이 줄어든다.

디리클레 함수의 진동의 합은 \(n\)이다. \(1/n\)을 곱하면 \(1\)이 되어 \(n\)과 무관하게 남는다. 서술 파트 4절의 표가 이 두 행이다.

여기서 \(b - a = 1\)이므로 마지막 열이 \(1\)로 찍혔다. 일반적으로는 \(b-a\)이다.

7. 라이브러리는 값을 돌려준다#

디리클레 함수는 적분 가능하지 않으므로 적분값이 존재하지 않는다. 그런데 수치 적분 라이브러리에 넣으면 값이 돌아온다.

먼저 이 함수를 코드로 쓸 수 없다는 것부터 확인한다. 부동소수는 모두 유리수이다. 유한한 이진 자릿수로 표현되므로 두 정수의 몫으로 정확히 쓸 수 있다.

# Every float is a rational number, so the code cannot see the irrational side
def is_rational_float(t):
    """A finite float is always a ratio of two integers, so this is always True."""
    return Fraction(float(t)) == float(t)


def dirichlet_as_code(t):
    """The Dirichlet function as far as floating point can express it."""
    return 1.0 if is_rational_float(t) else 0.0


for sample in (0.5, 1.0 / 3.0, float(np.sqrt(0.5)), float(np.pi / 4)):
    print(f"{sample!r:>22} -> rational : {is_rational_float(sample)}")
                   0.5 -> rational : True
    0.3333333333333333 -> rational : True
    0.7071067811865476 -> rational : True
    0.7853981633974483 -> rational : True

\(\sqrt{0.5}\)와 \(\pi/4\)는 무리수이다. 그러나 np.sqrt(0.5)가 돌려주는 것은 그 무리수가 아니라 그것에 가장 가까운 부동소수이며, 그 부동소수는 유리수이다. 따라서 dirichlet_as_code는 어떤 입력에도 \(1\)을 돌려준다.

이제 quad에 넣는다.

# scipy integrates the code, not the function
value, error = quad(dirichlet_as_code, 0.0, 1.0)

print("quad returned       :", value)
print("quad reported error :", f"{error:.3e}")
quad returned       : 1.0
quad reported error : 1.110e-14

quad가 \(1\)을 돌려주고 오차가 \(10^{-14}\) 수준이라고 보고한다. 이 값에는 의미가 없다.

quad는 유한개의 점에서 함숫값을 보고 그 값들로 답을 만든다. 유한개의 점은 모두 유리수이므로 quad가 실제로 적분한 것은 값이 항상 \(1\)인 상수함수이다. 그 함수의 적분은 정말로 \(1\)이고, 오차 보고도 그 함수에 대해서는 정확하다.

오차 보고가 작다는 것은 답이 옳다는 뜻이 아니다

quad가 보고하는 오차는 자기가 본 표본에 대한 추정이다. 보지 못한 곳에 대해서는 아무것도 말하지 않는다.

예제 5은 이 함수의 하적분이 \(0\)이고 상적분이 \(1\)임을 증명하였다. 두 값이 다르므로 적분값이 존재하지 않는다. 존재하지 않는 값을 라이브러리가 돌려준 것이며, 그것을 알아내는 방법은 정의로 돌아가는 것뿐이다.

6절의 표가 같은 것을 다른 방식으로 말한다. 진동의 합이 \(n\)에 비례하여 커지므로 \(U_n - L_n\)이 줄지 않는다.

마치며#

단조함수에서는 표본추출이 필요하지 않았다. (10.2)가 각 부분구간의 최댓값과 최솟값의 위치를 정해 주므로 끝점 두 개만 보면 된다.

(10.5)를 네 함수에서 확인하였다. 수치로도 맞았고, sympy가 계산한 합도 공식과 같았다. \(\sqrt{x}\)와 \(1/(1+x)\)에서는 \(\sum f(x_k)\)의 닫힌 식이 없는데도 차이는 정리되었다. 망원합이 개별 항을 모르고도 답을 주기 때문이다.

띠를 쌓는 그림에서 그 이유가 보였다. 폭이 모두 같으므로 띠들을 한 세로줄로 모을 수 있고, 쌓인 높이는 \(f(b)-f(a)\)로 \(n\)과 무관하다. \(n\)을 키우면 폭만 줄어든다.

디리클레 함수에서는 진동의 합이 \(n\)에 비례하여 커졌고 \(U_n - L_n\)이 줄지 않았다. 그림으로는 두 수평선처럼 보이지만 그림이 보여 주지 못하는 것이 보조정리 4이다. 그리고 quad는 값을 돌려주었다. 라이브러리가 답을 준다고 해서 그 값이 존재하는 것은 아니다.

서술 파트가 주장한 것과 이 노트북에서 그것을 확인한 코드를 나란히 둔다.

서술 파트의 내용

이 노트북의 코드

(10.2) — 단조성이 최댓값의 위치를 정한다

1절 monotone_sums가 표본 없이 끝점만 쓴다

(10.5)의 차이 공식

1절의 네 함수 대조표

(10.4)의 망원합

2절 symbolic_gap의 sp.summation 결과

차이가 직사각형 하나가 된다는 설명

3절 bottoms = np.cumsum(strips)로 쌓은 오른쪽 그림

\(\epsilon\)이 먼저이고 \(n\)이 나중이라는 점

4절 smallest_n(a, b, ends_gap, eps)의 서명

보조정리 4

5절 그림이 보여 주지 못하는 것

(10.11)의 진동의 합

6절에서 \(x^2\)은 \(1\), 디리클레는 \(n\)

예제 5이 적분 불가능이라는 것

7절 quad가 돌려준 \(1\)과 그 값이 무의미한 이유

더 해 볼 것#

  1. 1절의 cases에 \(f(x) = x^4\)을 \([0, 1.5]\)에서 추가해 보자. (10.5)가 주는 값이 얼마인지 먼저 손으로 계산하고 표와 맞는지 확인해 보자.

  2. 2절의 symbolic_gap에 \(1/(1+x)\) 대신 \(1/(1+x^2)\)을 넣어 보자. 이 함수는 \([0,3]\)에서 감소하므로 결과가 음수여야 한다. sympy가 합을 정리해 주는지 확인해 보자.

  3. 3절의 \(n\)을 \(4\)와 \(32\)로 바꾸어 오른쪽 그림이 어떻게 달라지는지 보자. 높이와 폭 중 무엇이 변하는가.

  4. 6절의 디리클레 열을 \([0,2]\)에서 다시 만들어 보자. 마지막 열이 \(1\)이 아니라 \(2\)가 되어야 한다. 어디를 고쳐야 하는가.

  5. 7절의 dirichlet_as_code를 유리수에서 \(0\), 무리수에서 \(1\)로 뒤집으면 quad가 무엇을 돌려주는가. 그 값도 무의미한가.

짝이 되는 서술

10회 · 적분 가능한 함수