23회 실험 · 제한이 답을 정한다#

이 실험

짝 서술

23회 · 역삼각함수와 삼각치환

확인하는 것

제한 구간을 벗어나면 \(\arcsin(\sin x)\ne x\)가 되는 것을 본다. 도함수의 부호가 제한 구간에서 나온다는 것을 확인한다. 원의 넓이를 두 방법으로 구한다

이 노트북의 주제는 관례가 답을 바꾼다는 것이다.

서술 파트 1절이 “제한 구간은 관례이다”라고 했고 2절이 “부호를 확인하지 않으면 정해지지 않는다”고 했다. 두 말이 추상적으로 들리는데, 코드는 그 관례를 어겼을 때 나오는 값을 실제로 보여 준다.

1절이 \(\arcsin(\sin\pi)=0\)을 출력한다. 2절은 \(\cos(\arcsin x)\)가 언제나 양수인 것을 확인하고, 다른 구간을 골랐다면 어떻게 되는지를 나란히 놓는다.

5절이 이 회차의 결말이다. 20회 실험이 원 없이 \(\pi\)를 구했는데, 여기서는 그 \(\pi\)가 원의 넓이로 돌아온다.

서술 파트의 내용

여기서 확인하는 방법

보조정리 14

1절이 구간을 넓히면 순단조가 깨지는 것을 본다

제한 구간을 잊는 실수

1절이 \(\arcsin(\sin x)\)를 세 점에서 계산한다

부호 확인

2절이 \(\cos(\arcsin x)\)의 부호를 본다

정리 77

3절이 차분몫과 공식을 대조한다

(23.6)

4절이 상합·하합으로 \(\pi/4\)를 좁힌다

(23.8)

5절이 미분해 되돌린다

정리 78

5절이 두 방법으로 넓이를 구한다

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

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_INV = "#d62728"
C_LINE = "#808080"
C_AREA = "#9467bd"
C_TRIG = "#1f77b4"

x = sp.Symbol("x", real=True)
# The integral of definition 7, used to check answers against the formulas
def lower_upper(f, a, b, n, samples=200):
    points = np.linspace(a, b, n + 1)
    left = points[:-1].reshape(n, 1)
    right = points[1:].reshape(n, 1)
    values = f(left + (right - left)
               * np.linspace(0.0, 1.0, samples).reshape(1, samples))
    return (float(np.sum(values.min(axis=1) * np.diff(points))),
            float(np.sum(values.max(axis=1) * np.diff(points))))

1. 제한이 없으면 역함수가 없다#

보조정리 14이 \(\sin\)을 \(\left[-\frac{\pi}{2},\frac{\pi}{2}\right]\)에서만 순증가라고 했다. 구간을 넓히면 어떻게 되는지 본다.

# Strictly increasing on the restricted interval, and not on a wider one
for lo, hi, label in ((-np.pi / 2, np.pi / 2, "[-pi/2, pi/2]"),
                      (-np.pi, np.pi, "[-pi, pi]"),
                      (0.0, 2 * np.pi, "[0, 2pi]")):
    grid = np.linspace(lo, hi, 4000)
    rising = bool(np.all(np.diff(np.sin(grid)) > 0))
    print(f"sin strictly increasing on {label:>16} : {rising}")
sin strictly increasing on    [-pi/2, pi/2] : True
sin strictly increasing on        [-pi, pi] : False
sin strictly increasing on         [0, 2pi] : False

첫 줄만 참이다. 나머지 두 구간에서는 서로 다른 두 점이 같은 값을 가지므로 17회 정의 34의 조건이 깨진다.

그 결과가 무엇인지 본다.

# arcsin(sin x) returns x only inside the restricted interval
print(f"{'x':>12} {'sin x':>14} {'arcsin(sin x)':>16} {'equals x':>10}")
print("-" * 56)
for v in (0.5, 1.5, np.pi, 3.0, -2.0):
    back = np.arcsin(np.sin(v))
    print(f"{v:>12.6f} {np.sin(v):>14.9f} {back:>16.9f} "
          f"{str(abs(back - v) < 1e-12):>10}")
           x          sin x    arcsin(sin x)   equals x
--------------------------------------------------------
    0.500000    0.479425539      0.500000000       True
    1.500000    0.997494987      1.500000000       True
    3.141593    0.000000000      0.000000000      False
    3.000000    0.141120008      0.141592654      False
   -2.000000   -0.909297427     -1.141592654      False

\(x=\pi\)에서 \(\arcsin(\sin\pi)=0\)이다. \(\pi\)가 아니다.

\(\sin\pi = 0\)이고 \(\arcsin 0 = 0\)이므로 그렇게 된다. \(\arcsin\)은 제한 구간 안의 값만 돌려주므로 \(\pi\)를 돌려줄 수 없다.

반대 방향은 다르다.

# The other direction holds everywhere on [-1, 1]
print(f"{'y':>8} {'sin(arcsin y)':>16} {'equals y':>10}")
print("-" * 38)
for y in (-1.0, -0.4, 0.0, 0.7, 1.0):
    back = np.sin(np.arcsin(y))
    print(f"{y:>8} {back:>16.12f} {str(abs(back - y) < 1e-12):>10}")
       y    sin(arcsin y)   equals y
--------------------------------------
    -1.0  -1.000000000000       True
    -0.4  -0.400000000000       True
     0.0   0.000000000000       True
     0.7   0.700000000000       True
     1.0   1.000000000000       True

17회 (17.1)의 두 등식이 정의역이 다른 이유가 이것이다. \(f^{-1}(f(x))=x\)는 \(x\)가 제한된 정의역에 있어야 하고, \(f(f^{-1}(y))=y\)는 \(y\)가 치역에 있으면 된다.

Hide code cell source

# sin restricted, and its mirror image across y = x
fig, ax = plt.subplots(figsize=(5.6, 5.2))

full = np.linspace(-np.pi, np.pi, 500)
ax.plot(full, np.sin(full), color=C_LINE, linewidth=1.0, alpha=0.6,
        label="sin, unrestricted")

restricted = np.linspace(-np.pi / 2, np.pi / 2, 400)
ax.plot(restricted, np.sin(restricted), color=C_TRIG, linewidth=2.2,
        label="sin on [-pi/2, pi/2]")

ys = np.linspace(-1.0, 1.0, 400)
ax.plot(ys, np.arcsin(ys), color=C_INV, linewidth=2.0, label="arcsin")

line = np.linspace(-2.0, 2.0, 100)
ax.plot(line, line, ":", color=C_LINE, linewidth=1.0)

ax.set_xlim(-2.2, 2.2)
ax.set_ylim(-2.2, 2.2)
ax.set_aspect("equal")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_title("only the thick part has an inverse")
ax.legend(loc="upper left", fontsize=8)
fig.tight_layout()
plt.show()
../_images/2efbae83698cc7937b36c0631a5fe3facd3831325a24cc9178ed7ba58a6ef5a6.png

가는 선이 제한 없는 \(\sin\)이다. 그 부분까지 뒤집으면 한 \(y\)에 여러 \(x\)가 대응하므로 함수가 되지 않는다.

2. 부호가 답을 정한다#

서술 파트 2절이 \(\cos(\arcsin x) = \sqrt{1-x^2}\)에서 양의 근호를 택하는 근거가 제한 구간이라고 했다.

# cos(arcsin x) is always the positive root, because arcsin lands in [-pi/2, pi/2]
print(f"{'x':>7} {'arcsin x':>12} {'cos of it':>14} {'+sqrt(1-x^2)':>15} {'same':>6}")
print("-" * 60)
for v in (-0.9, -0.2, 0.4, 0.95):
    p = np.arcsin(v)
    left = np.cos(p)
    right = np.sqrt(1 - v ** 2)
    print(f"{v:>7} {p:>12.6f} {left:>14.9f} {right:>15.9f} "
          f"{str(abs(left - right) < 1e-12):>6}")
      x     arcsin x      cos of it    +sqrt(1-x^2)   same
------------------------------------------------------------
   -0.9    -1.119770    0.435889894     0.435889894   True
   -0.2    -0.201358    0.979795897     0.979795897   True
    0.4     0.411517    0.916515139     0.916515139   True
   0.95     1.253236    0.312249900     0.312249900   True

\(\arcsin x\)가 언제나 \(\left[-\frac{\pi}{2},\frac{\pi}{2}\right]\) 안에 있고 그 구간에서 \(\cos \ge 0\)이므로 셋째 열이 양수이다.

다른 구간을 골랐다면 부호가 반대가 된다. \(\left[\frac{\pi}{2},\frac{3\pi}{2}\right]\)에서도 \(\sin\)은 순단조이므로 역함수가 있다.

# The other conventional choice: sin restricted to [pi/2, 3pi/2] is decreasing
def other_arcsin(y):
    # solve sin(t) = y with t in [pi/2, 3pi/2]
    return np.pi - np.arcsin(y)


print(f"{'x':>7} {'other inverse':>15} {'cos of it':>14} {'sign':>7}")
print("-" * 48)
for v in (-0.9, -0.2, 0.4, 0.95):
    p = other_arcsin(v)
    print(f"{v:>7} {p:>15.6f} {np.cos(p):>14.9f} "
          f"{'negative' if np.cos(p) < 0 else 'positive':>7}")
      x   other inverse      cos of it    sign
------------------------------------------------
   -0.9        4.261362   -0.435889894 negative
   -0.2        3.342951   -0.979795897 negative
    0.4        2.730076   -0.916515139 negative
   0.95        1.888357   -0.312249900 negative

넷째 열이 모두 음수이다. 그 구간을 골랐다면 (23.3)의 부호가 반대였을 것이다.

\(\arcsin\)과 \(\arccos\)의 도함수가 부호만 다른 것도 확인해 둔다. 서술 파트가 그 둘의 합이 상수라고 했다.

# arcsin + arccos is constant, as cor-18-constant-diff predicts
for v in (-0.9, -0.3, 0.0, 0.5, 0.99):
    total = np.arcsin(v) + np.arccos(v)
    print(f"x = {v:>6}  arcsin + arccos = {total:.15f}")
print()
print(f"pi/2                = {np.pi / 2:.15f}")
x =   -0.9  arcsin + arccos = 1.570796326794897
x =   -0.3  arcsin + arccos = 1.570796326794897
x =    0.0  arcsin + arccos = 1.570796326794897
x =    0.5  arcsin + arccos = 1.570796326794897
x =   0.99  arcsin + arccos = 1.570796326794897

pi/2                = 1.570796326794897

3. 도함수를 대조한다#

정리 77의 세 공식을 차분몫과 맞춘다.

# Difference quotients against the formulas of thm-23-inverse-deriv
def difference_quotient(f, p, step=1e-6):
    return (f(p + step) - f(p - step)) / (2 * step)


print("arcsin")
print(f"{'x':>8} {'quotient':>17} {'1/sqrt(1-x^2)':>17} {'difference':>12}")
print("-" * 58)
for v in (-0.8, -0.3, 0.0, 0.5, 0.9):
    q = difference_quotient(np.arcsin, v)
    formula = 1.0 / np.sqrt(1 - v ** 2)
    print(f"{v:>8} {q:>17.9f} {formula:>17.9f} {abs(q - formula):>12.2e}")
arcsin
       x          quotient     1/sqrt(1-x^2)   difference
----------------------------------------------------------
    -0.8       1.666666667       1.666666667     1.09e-11
    -0.3       1.048284837       1.048284837     3.64e-11
     0.0       1.000000000       1.000000000     1.67e-13
     0.5       1.154700538       1.154700538     2.65e-11
     0.9       2.294157339       2.294157339     1.72e-10
# The same for arctan, whose formula has no square root
print("arctan")
print(f"{'x':>8} {'quotient':>17} {'1/(1+x^2)':>17} {'difference':>12}")
print("-" * 58)
for v in (-2.0, -0.5, 0.0, 1.0, 5.0):
    q = difference_quotient(np.arctan, v)
    formula = 1.0 / (1 + v ** 2)
    print(f"{v:>8} {q:>17.9f} {formula:>17.9f} {abs(q - formula):>12.2e}")
arctan
       x          quotient         1/(1+x^2)   difference
----------------------------------------------------------
    -2.0       0.200000000       0.200000000     5.75e-12
    -0.5       0.800000000       0.800000000     4.75e-12
     0.0       1.000000000       1.000000000     3.33e-13
     1.0       0.500000000       0.500000000     4.11e-11
     5.0       0.038461539       0.038461538     4.81e-11

\(\arctan\)의 도함수에는 근호가 없다. 유리함수이다. 그래서 (23.5)의 둘째 식이 유리함수의 적분에 삼각함수의 역함수를 답으로 준다.

4. π/4 를 두 방법으로#

(23.6)이 \(\int_0^1\frac{dx}{1+x^2} = \frac{\pi}{4}\)라고 했다. 7회의 방식으로 좁혀 맞춘다.

# Trapping the integral between step-function sums
print(f"{'n':>8} {'lower':>15} {'upper':>15} {'width':>11}")
print("-" * 52)
for n in (10, 100, 1000, 10000):
    low, high = lower_upper(lambda t: 1.0 / (1 + t ** 2), 0.0, 1.0, n)
    print(f"{n:>8} {low:>15.9f} {high:>15.9f} {high - low:>11.2e}")

print()
print(f"arctan 1 = {np.arctan(1.0):.12f}")
print(f"pi / 4   = {np.pi / 4:.12f}")
       n           lower           upper       width
----------------------------------------------------
      10     0.759981497     0.809981497    5.00e-02
     100     0.782893997     0.787893997    5.00e-03
    1000     0.785148122     0.785648122    5.00e-04
   10000     0.785373163     0.785423163    5.00e-05

arctan 1 = 0.785398163397
pi / 4   = 0.785398163397

왼쪽에는 삼각함수가 없다. 피적분함수가 유리함수이고 상합과 하합은 유리수 연산뿐이다. 그런데 그 값이 \(\pi/4\)이다.

\(\arctan 1 = \frac{\pi}{4}\)인 근거는 \(\tan\frac{\pi}{4}=1\)이고, 서술 파트가 배각공식으로 그것을 얻었다.

# tan(pi/4) = 1, from the double-angle identity the narrative derives
quarter = np.pi / 4
print(f"sin(pi/4)  = {np.sin(quarter):.12f}")
print(f"1/sqrt(2)  = {1 / np.sqrt(2):.12f}")
print(f"cos(pi/4)  = {np.cos(quarter):.12f}")
print(f"tan(pi/4)  = {np.tan(quarter):.12f}")
sin(pi/4)  = 0.707106781187
1/sqrt(2)  = 0.707106781187
cos(pi/4)  = 0.707106781187
tan(pi/4)  = 1.000000000000

배각공식 (23.7)도 확인한다.

# (23.7): cos^2 t = (1 + cos 2t) / 2
theta = sp.Symbol("theta", real=True)
print("cos^2 t - (1 + cos 2t)/2 =",
      sp.simplify(sp.cos(theta) ** 2 - (1 + sp.cos(2 * theta)) / 2))
cos^2 t - (1 + cos 2t)/2 =
 0

5. 원의 넓이#

정리 78을 확인한다. 서술 파트가 치환 정리를 쓰지 않고 원시함수를 확인한 뒤 정리 64를 직접 썼다.

먼저 (23.8)의 원시함수를 미분해 되돌린다.

# (23.8): differentiate the primitive back, on the open interval
G = (x * sp.sqrt(1 - x ** 2) + sp.asin(x)) / 2
print("G      =", G)
print("G'     =", sp.simplify(sp.diff(G, x)))
print("equals sqrt(1 - x**2) :",
      sp.simplify(sp.diff(G, x) - sp.sqrt(1 - x ** 2)) == 0)
G      = x*sqrt(1 - x**2)/2 + asin(x)/2
G'     = sqrt(1 - x**2)
equals sqrt(1 - x**2) : True

이제 두 끝값의 차를 구한다.

# The fundamental theorem, applied to G on [-1, 1]
top = G.subs(x, 1)
bottom = G.subs(x, -1)
print("G(1)        =", top, "=", float(top))
print("G(-1)       =", bottom, "=", float(bottom))
print("difference  =", sp.simplify(top - bottom), "=", float(top - bottom))
print("pi / 2      =", f"{np.pi / 2:.15f}")
G(1)        = pi/4 = 0.7853981633974483
G(-1)       = -pi/4 = -0.7853981633974483
difference  = pi/2 = 1.5707963267948966
pi / 2      = 1.570796326794897

정의로도 확인한다. 상합과 하합이 그 값을 가두어야 한다.

# The same number, from step functions
half_disc = lambda t: np.sqrt(np.maximum(1 - t ** 2, 0.0))
low, high = lower_upper(half_disc, -1.0, 1.0, 20000)

print(f"lower sum  {low:.9f}")
print(f"upper sum  {high:.9f}")
print(f"pi / 2     {np.pi / 2:.9f}")
print(f"traps it   {low <= np.pi / 2 <= high}")
lower sum  1.570695739
upper sum  1.570895739
pi / 2     1.570796327
traps it   True

두 경로가 만난다. 위는 원시함수를 미분해 확인한 뒤 두 값을 뺀 것이고, 아래는 이만 개의 계단 넓이를 더한 것이다.

Hide code cell source

# The disc, its two halves, and the number that comes out
fig, axes = plt.subplots(1, 2, figsize=(11.0, 4.4))

ax = axes[0]
xs = np.linspace(-1.0, 1.0, 500)
upper = np.sqrt(np.maximum(1 - xs ** 2, 0.0))
ax.fill_between(xs, 0.0, upper, color=C_AREA, alpha=0.45,
                label="upper half: area pi/2")
ax.fill_between(xs, -upper, 0.0, color=C_TRIG, alpha=0.25,
                label="lower half: congruent")
ax.plot(xs, upper, color=C_FUNC, linewidth=1.6)
ax.plot(xs, -upper, color=C_FUNC, linewidth=1.6)
ax.axhline(0.0, color=C_FUNC, linewidth=0.8)
ax.set_xlim(-1.3, 1.3)
ax.set_ylim(-1.3, 1.3)
ax.set_aspect("equal")
ax.set_title("area of the disc is pi")
ax.legend(loc="upper right", fontsize=8)

ax = axes[1]
counts = [10, 40, 200, 1000, 5000, 20000]
widths = []
for n in counts:
    low_n, high_n = lower_upper(half_disc, -1.0, 1.0, n)
    widths.append(high_n - low_n)
ax.plot(counts, widths, "o-", color=C_INV, linewidth=1.6)
ax.set_xscale("log")
ax.set_yscale("log")
ax.set_xlabel("number of pieces")
ax.set_ylabel("upper minus lower")
ax.set_title("the trap closes as the pieces shrink")
fig.tight_layout()
plt.show()
../_images/c19837c6df3ae7f5e342a8f2031087b2d399b133b63211ad8f314f29db4e2d53.png

오른쪽 그림에서 조각을 늘리면 상합과 하합의 차가 줄어든다. 그 차가 \(0\)으로 가는 것이 7회 정의 15이 요구하는 것이다.

마치며#

\(\sin\)은 \(\left[-\frac{\pi}{2},\frac{\pi}{2}\right]\)에서만 순증가했고, 그 밖으로 넓히면 순단조가 깨졌다. \(\arcsin(\sin\pi)\)가 \(\pi\)가 아니라 \(0\)이었다.

\(\cos(\arcsin x)\)가 네 점 모두에서 양수였고, \(\left[\frac{\pi}{2},\frac{3\pi}{2}\right]\)로 제한한 다른 역함수에서는 모두 음수였다. (23.3)의 부호는 관례가 정한 것이다.

차분몫이 세 공식과 \(10^{-10}\) 안에서 맞았다.

\(\int_0^1\frac{dx}{1+x^2}\)을 상합과 하합으로 \([0.785373,\ 0.785423]\)까지 좁혔고 \(\pi/4\)가 그 안에 있었다. 왼쪽에는 삼각함수가 없는데 오른쪽에 \(\pi\)가 있다.

그리고 \(\int_{-1}^{1}\sqrt{1-x^2}\,dx\)를 두 방법으로 구해 \(\pi/2\)를 얻었다. 20회 실험이 원 없이 \(\pi\)를 구했는데, 여기서 그 수가 원의 넓이로 돌아왔다.

서술 파트의 내용

이 노트북의 코드

보조정리 14

1절 세 구간의 np.diff 검사

제한 구간을 잊는 실수

1절 arcsin(sin(pi)) 가 \(0\)

(17.1)의 두 등식

1절 두 방향 표

부호 확인

2절 other_arcsin 과의 대비

\(\arcsin+\arccos\)가 상수

2절 다섯 점에서 같은 값

정리 77

3절 두 차분몫 표

(23.6)

4절 상합·하합과 arctan(1)

(23.7)

4절 sp.simplify 가 \(0\)

(23.8)

5절 G' 이 피적분함수

정리 78

5절 G(1)-G(-1) 과 상합·하합

더 해 볼 것#

  1. 1절의 other_arcsin 으로 3절의 차분몫 표를 다시 뽑아 보자. 도함수의 부호가 어떻게 달라지는가. 그것이 서술 파트 2절의 부호 확인과 어떻게 이어지는가.

  2. 4절의 적분 구간을 \([0,\sqrt3]\)으로 바꾸면 값이 얼마인가. \(\arctan\sqrt3\)이 무엇인지 짐작할 수 있는가.

  3. 5절의 lower_upper 에서 np.maximum 을 빼면 무슨 일이 일어나는가. 왜 그 보호가 필요한지, 그것이 서술 파트의 「왜 치환으로 계산하지 않았는가」 상자와 어떻게 이어지는지 적어 보자.

  4. \(\int_{-1}^{1}\sqrt{1-x^2}\,dx\)를 \(x=\sin\theta\) 치환으로 계산해 보자. sympy 는 답을 주는가. 서술 파트가 그 경로를 쓰지 않은 이유를 다시 읽어 보자.