드론 관제실의 수치해석 기초: NumPy로 근사해 풀기
·
강의 개요
1
공식이 없으면 한 걸음씩: 수치해석이란
2
0.1 + 0.2가 0.3이 아닌 날: 컴퓨터 속 실수
3
근사의 대가: 테일러 급수와 절단 오차
4
구간을 반으로: 이분법과 가위치법
5
접선을 타고 내려가기: 뉴턴-랩슨과 할선법
6
가장 멀리 나는 속도: 1차원 최적화
7
배송 허브 세 곳의 물량 맞추기: 가우스 소거와 피벗
8
지도가 커지면: LU 분해, 반복법, 조건수
9
센서 기록에 선 긋기: 최소제곱 회귀
10
비어 있는 시각 채우기: 보간과 스플라인
11
얼마나 날았나: 수치 적분
12
처음 문제로 돌아가기: 미분방정식의 수치해
운항 일지
#05 · 매 비행 전에
문제
오늘 밤 배송할 상자가 300개다. 상자마다 무게(4~20 kg)와 받는 곳에서 허용하는 4초 뒤 속도(8, 10, 12 m/s)가 다르다. 허브의 작은 컴퓨터가 비행 직전에 상자마다 낙하산 방정식을 풀어야 하는데, 이분법으로는 상자 하나에 수십 번씩 계산해야 한다.
목표
한 번에 근으로 뛰어드는 뉴턴-랩슨 방법과 도함수가 필요 없는 할선법을 익혀, 같은 정확도를 몇 배 적은 계산으로 얻는다. 언제 실패하는지도 안다.
하늘상자 새벽 배송 관제실
4교시 다음 날 아침, 민재 팀장이 배송 목록을 출력해 들고 왔습니다. "어제 방법 좋던데요. 그런데 상자가 300개예요. 무게도 다르고, 약 상자는 8 m/s, 생필품은 12 m/s까지 괜찮대요. 허브 컴퓨터가 이륙 직전에 다 풀어야 해요."
서윤이 계산합니다. 소수 열째 자리까지 구하면 이분법은 상자 하나에 30번 넘게 자릅니다. 300개면 만 번이 넘습니다. "구간을 반씩 줄이는 건 너무 조심스러운 것 같아요. 그래프가 이렇게 매끈한데, 근이 어느 쪽에 있는지 기울기를 보면 알 수 있지 않을까요?"
오 박사가 고개를 끄덕입니다. "그게 뉴턴이 한 생각이야. 지금 있는 곳에서 접선을 긋고, 접선이 0이 되는 곳으로 바로 뛰어. 대신 구간이라는 안전망은 없어. 빠른 만큼 엉뚱한 데로 뛰기도 하지."
오늘은 구간 없이 한 점(또는 두 점)에서 출발하는 열린 방법(open method) 을 배웁니다. 빠르기를 숫자로 재 보고, 실패하는 모습도 일부러 만들어 봅니다.
지금 에 서 있다고 합시다. 곡선 위의 점 에서 접선(tangent line) 을 긋습니다. 접선은 그 점 근처에서 곡선과 거의 같은 직선이고, 기울기는 도함수 입니다.
곡선 대신 이 직선이 0이 되는 곳을 찾는 것은 쉽습니다. 으로 놓고 에 대해 풀면 다음 점이 나옵니다.
이것이 뉴턴-랩슨 방법(Newton–Raphson method) 입니다. 줄여서 뉴턴법이라고 합니다. 는 "근까지 남은 높이", 는 "한 칸 갈 때 내려가는 높이"이니, 그 몫은 "몇 칸 가야 0에 닿는가"입니다. 3교시의 테일러 급수로 보면, 를 1차 항까지만 남겨 근사한 것입니다.
손으로 두 걸음. 의 근은 입니다. 이니 입니다. 에서 출발합니다.
| 걸음 | ||||
|---|---|---|---|---|
| 1 | 1 | −1 | 2 | 1 − (−1)/2 = 1.5 |
| 2 | 1.5 | 0.25 | 3 | 1.5 − 0.25/3 = 1.41667 |
이니 두 걸음 만에 소수 둘째 자리까지 맞았습니다. 한 걸음 더 가 봅니다.
>>> import numpy as np
>>> x = 1.0
>>> for k in range(1, 4):
... x = x - (x**2 - 2) / (2 * x)
... print(k, x)
1 1.5
2 1.4166666666666667
3 1.4142156862745099
>>> np.sqrt(2)
np.float64(1.4142135623730951)세 걸음 만에 소수 다섯째 자리까지 맞습니다. 계산기가 제곱근을 구하는 방법이 실제로 이것과 같은 계열입니다.
낙하산 문제에 쓰기. 뉴턴법에는 도함수가 필요합니다. 낙하산 함수 를 미분하면(연쇄 법칙을 두 번 쓰는 과정은 건너뜁니다) 이렇게 됩니다. 로 줄여 썼고, 입니다.
손으로 구한 도함수는 틀리기 쉬우니, 3교시의 중앙 차분으로 한 번 확인하고 씁니다.
>>> g, m = 9.81, 12
>>> def f(cd):
... return np.sqrt(g * m / cd) * np.tanh(np.sqrt(g * cd / m) * 4) - 10
>>> def fp(cd):
... s = np.sqrt(g * cd / m) * 4
... return np.sqrt(g * m / cd) / (2 * cd) * (s / np.cosh(s)**2 - np.tanh(s))
>>> h = 1e-6
>>> round(fp(0.5), 6), round((f(0.5 + h) - f(0.5 - h)) / (2 * h), 6)
(np.float64(-14.229019), np.float64(-14.229019))
>>> round(f(0.5), 4), round(fp(0.5), 4)
(np.float64(5.1608), np.float64(-14.229))
>>> round(0.5 - f(0.5) / fp(0.5), 4)
np.float64(0.8627)에서 목표보다 5.16 m/s 빠르고, 를 1 늘리면 14.2 m/s 느려지는 기울기입니다. 그러니 만큼 늘리면 됩니다. 첫 걸음이 0.5에서 0.8627로 갑니다.
걸음마다 참값과의 오차를 재 봅니다. root는 4교시에 구해 둔 참값입니다.
>>> root = 1.1753511424325662
>>> x = 0.5
>>> errs = []
>>> for k in range(1, 7):
... x = x - f(x) / fp(x)
... errs.append(abs(x - root))
... print(k, round(x, 12), f"{errs[-1]:.1e}")
1 0.862694367949 3.1e-01
2 1.11151584754 6.4e-02
3 1.172772961161 2.6e-03
4 1.175346968315 4.2e-06
5 1.175351142422 1.1e-11
6 1.175351142433 1.3e-15오차 열의 지수를 보세요. −1, −2, −3, −6, −11. 근에 가까워지자 지수가 두 배씩 커집니다. 맞는 자릿수가 걸음마다 두 배가 된다는 뜻입니다. 이분법은 자릿수 하나에 3.3번이 필요했는데, 뉴턴법은 마지막 두 걸음에 자릿수 열한 개를 얻었습니다.
식으로는 다음 오차가 지금 오차의 제곱에 비례합니다. 이런 빠르기를 2차 수렴(quadratic convergence) 이라고 합니다.
>>> e = np.array(errs[:5])
>>> np.round(e[1:] / e[:-1]**2, 2)
array([0.65, 0.63, 0.63, 0.63])이 0.63으로 거의 일정합니다. 오차가 이면 다음은 , 그다음은 쯤입니다. 대신 이 말은 근 가까이에서만 맞습니다. 첫 두 걸음은 오차가 크게 줄지 않았습니다. 멀리서는 접선이 곡선과 많이 다르기 때문입니다.
실제 문제에서는 참값을 모르니, 4교시와 같은 상대 오차 어림으로 멈추는 함수로 묶어 둡니다.
>>> def newton(f, fp, x, es=1e-10, maxit=50):
... for k in range(1, maxit + 1):
... x_new = x - f(x) / fp(x)
... if abs(x_new - x) < es * abs(x_new):
... return x_new, k
... x = x_new
... return x, maxit
>>> newton(f, fp, 0.5)
(np.float64(1.1753511424325649), 6)
>>> newton(f, fp, 2.0)
(np.float64(1.1753511424325653), 7)이분법이 34번 자른 정확도()를 6번 만에 얻었습니다. 출발점이 2.0처럼 근에서 조금 멀어도 한 번 더 걸릴 뿐입니다.
아래 실습대에서 x축 위의 손잡이 x₀를 끌어 출발점을 정하세요. 남색 곡선이 f, 주황 선이 이번 단계의 접선입니다. 재생을 누르면 곡선 위의 점에서 접선이 x축까지 그어지고, 닿은 곳에서 곡선으로 올라가 다음 점이 됩니다. 지난 단계의 선은 회색으로 남습니다. '할선'을 누르면 청록 손잡이 x₁이 생기고, 두 점을 잇는 직선을 씁니다. 아래 표의 마지막 열은 수렴했을 때 근과의 오차, 수렴하지 못했을 때는 이번에 움직인 거리 |Δx|입니다.
| k | x | f(x) | 오차 |x − 근| |
|---|---|---|---|
| 0 | 0.5 | 5.16 | 0.68 |
| 1 | 0.8626943679 | 1.65 | 0.31 |
| 2 | 1.1115158475 | 0.281 | 0.064 |
| 3 | 1.1727729612 | 0.0109 | 0.0026 |
| 4 | 1.1753469683 | 1.76e-5 | 4.2e-6 |
| 5 | 1.1753511424 | 4.62e-11 | 1.1e-11 |
| 6 | 1.1753511424 | 1.78e-15 | 1.3e-15 |
| 7 | 1.1753511424 | 0 | 8.9e-16 |
x₀ = 0.5
f(x₀) = 5.16
계산 횟수: f 0번, f′ 0번
x₀ = 0.5에서 출발합니다. 재생을 누르면 접선을 따라 내려갑니다.
NumPy로 확인하기
실습대에서 값을 정할 때마다, 서버의 NumPy가 같은 숫자로 계산한 결과입니다.
>>> import numpy as np
>>> def f(cd):
... return np.sqrt(9.81 * 12 / cd) * np.tanh(np.sqrt(9.81 * cd / 12) * 4) - 10
...
>>> def fp(cd):
... s = np.sqrt(9.81 * cd / 12) * 4
... return np.sqrt(9.81 * 12 / cd) / (2 * cd) * (s / np.cosh(s)**2 - np.tanh(s))
...
>>> x = 0.5
# 뉴턴: 접선이 x축과 만나는 곳으로. 줄마다 k, x_k, 이번에 움직인 거리
>>> for k in range(1, 13):
... d = fp(x)
... if d == 0:
... print(k, "fp(x) = 0")
... break
... x_new = x - f(x) / d
... print(k, round(x_new, 10), f"{abs(x_new - x):.1e}")
... if abs(x_new - x) < 1e-12 or not abs(x_new) < 1e6 or x_new <= 0:
... break
... x = x_new
...
1 0.8626943679 3.6e-01
2 1.1115158475 2.5e-01
3 1.1727729612 6.1e-02
4 1.1753469683 2.6e-03
5 1.1753511424 4.2e-06
6 1.1753511424 1.1e-11
7 1.1753511424 4.4e-16구간이라는 안전망이 없으니, 뉴턴법은 출발점과 함수 모양에 따라 엉뚱하게 움직일 수 있습니다. 걸음마다 지나간 점을 모아 보는 작은 함수를 만들어 네 가지 실패를 봅니다.
>>> def path(f, fp, x, n):
... xs = [x]
... for _ in range(n):
... x = x - f(x) / fp(x)
... xs.append(round(float(x), 4))
... return xs
>>> def c3(x):
... return x**3 - 3*x + 1
>>> def c3p(x):
... return 3*x**2 - 3
>>> path(c3, c3p, 1.0, 1)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> path(c3, c3p, 0.9, 6)
[0.9, -0.8035, 1.9166, 1.631, 1.5415, 1.5322, 1.5321]
>>> path(c3, c3p, 0.2, 4)
[0.2, 0.3417, 0.3473, 0.3473, 0.3473]① 평평한 곳 (). 은 에서 기울기가 0이라 접선이 x축과 만나지 않습니다. 0으로 나누게 됩니다. 정확히 0이 아니라도 0.9처럼 평평한 곳 근처에서 출발하면 접선이 거의 누워 있어서 멀리 튕겨 나갑니다.
② 다른 근으로. 그렇게 튕겨 나간 0.9는 −0.80, 1.92를 거쳐 1.5321로 갔습니다. 0.9에서 가장 가까운 근은 0.3473인데 말입니다. 0.2에서 출발하면 0.3473으로 바로 갑니다. 근이 여럿이면 어느 근에 닿을지는 출발점이 정합니다.
>>> path(lambda x: x**3 - 2*x + 2, lambda x: 3*x**2 - 2, 0.0, 6)
[0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0]
>>> path(np.arctan, lambda x: 1 / (1 + x**2), 1.5, 5)
[1.5, -1.6941, 2.3211, -5.1141, 32.2957, -1575.317]
>>> path(np.arctan, lambda x: 1 / (1 + x**2), 1.3, 5)
[1.3, -1.1616, 0.8589, -0.3742, 0.034, -0.0]
>>> path(f, fp, 3.0, 1)
[3.0, -0.5787]③ 진동. 를 0에서 출발하면 0의 접선은 1로, 1의 접선은 다시 0으로 보냅니다. 영원히 둘 사이를 오갑니다.
④ 발산. 는 근이 0 하나뿐인 얌전한 함수지만, 멀리 갈수록 평평해집니다. 1.5에서 출발하면 접선이 반대쪽으로 더 멀리 보내고, 그 점은 더 평평해서 더 멀리 보냅니다. 1.3에서 출발하면 반대로 지그재그로 줄어들며 0에 닿습니다. 경계는 약 1.39입니다.
낙하산 함수도 예외가 아닙니다. 에서 출발하면 곡선이 거의 평평해서 첫 접선이 로 떨어집니다. 음수 항력 계수에서는 제곱근을 계산할 수 없어 nan이 나옵니다.
오 박사의 한마디
뉴턴법은 좋은 출발점을 받았을 때만 빠른 거야. 우리 문제에서는 물리가 출발점을 알려 줘. 4초면 거의 종단 속도에 닿으니까, 종단 속도가 허용 속도가 되는 cd = gm/v²부터 시작하면 돼.
뉴턴법의 약점은 하나 더 있습니다. 도함수를 손으로 구해야 합니다. 낙하산 함수의 만 해도 한참 걸렸고, 실무의 함수는 다른 프로그램을 수천 줄 돌린 결과일 때가 많아 식으로 미분할 수가 없습니다.
해결책은 3교시에서 이미 보았습니다. 기울기를 차분으로 어림하는 것입니다. 그런데 새로 를 계산하지 않고, 이미 계산한 지난 두 점으로 기울기를 어림하면 공짜입니다.
두 점을 잇는 직선(할선, secant line)이 x축과 만나는 곳으로 가니 할선법(secant method) 이라고 합니다. 4교시의 가위치법과 식이 같습니다. 차이는 가위치법은 부호가 다른 두 끝을 지키고, 할선법은 그냥 가장 최근 두 점을 쓴다는 것입니다. 구간을 버렸으니 빠르지만, 뉴턴법처럼 길을 잃을 수 있습니다.
>>> def secant(f, x0, x1, es=1e-10, maxit=50):
... f0, f1 = f(x0), f(x1)
... for k in range(1, maxit + 1):
... x2 = x1 - f1 * (x1 - x0) / (f1 - f0)
... if abs(x2 - x1) < es * abs(x2):
... return x2, k
... x0, f0 = x1, f1
... x1, f1 = x2, f(x2)
... return x1, maxit
>>> secant(f, 0.5, 1.0)
(np.float64(1.1753511424325653), 7)
>>> x0, x1 = 0.5, 1.0
>>> for k in range(2, 9):
... x0, x1 = x1, x1 - f(x1) * (x1 - x0) / (f(x1) - f(x0))
... print(k, round(x1, 12), f"{abs(x1 - root):.1e}")
2 1.096407460194 7.9e-02
3 1.166522335881 8.8e-03
4 1.174911217795 4.4e-04
5 1.175348702786 2.4e-06
6 1.175351141759 6.7e-10
7 1.175351142433 1.8e-15
8 1.175351142433 8.9e-16secant 함수는 걸음마다 를 한 번만 새로 계산하도록 짰습니다. 지난 값 f0, f1을 기억해 두기 때문입니다. 그 아래 짧은 반복문은 실습대의 NumPy 칸과 같은 모양으로, 읽기 쉽게 매번 다시 계산합니다.
오차 지수는 −2, −3, −4, −6, −10, −15입니다. 두 배까지는 아니지만 매번 약 1.6배씩 커집니다. 할선법의 수렴 차수는 황금비 로, 초선형 수렴(superlinear convergence) 이라고 부릅니다.
한 걸음만 보면 뉴턴법(2차)이 빠르지만, 뉴턴법은 한 걸음에 와 를 둘 다 계산하고 할선법은 하나만 계산합니다. 계산이 만큼 비싸다면, 같은 계산량으로 할선법은 두 걸음을 가서 자릿수가 배가 됩니다. 계산 한 번당으로는 할선법이 더 빠를 때가 많습니다. 도함수 식이 복잡한 낙하산 문제가 바로 그런 경우입니다.
근 찾기를 보는 또 다른 눈이 있습니다. 을 모양으로 고쳐 쓰고, 를 되풀이하는 것입니다. 를 만족하는 점을 고정점(fixed point) 이라 하고, 이 방법을 고정점 반복(fixed-point iteration) 이라고 합니다. 뉴턴법도 인 고정점 반복입니다.
낙하산 식 의 양변을 제곱해 에 대해 정리하면 이런 모양이 됩니다.
>>> def gfix(cd):
... return g * m / 100 * np.tanh(np.sqrt(g * cd / m) * 4)**2
>>> x = 0.5
>>> for k in range(1, 5):
... x = gfix(x)
... print(k, round(x, 10))
1 1.1492470191
2 1.1751820803
3 1.1753501002
4 1.175351136
>>> h = 1e-6
>>> round((gfix(root + h) - gfix(root - h)) / (2 * h), 4)
np.float64(0.0062)아주 빨리 수렴합니다. 그런데 같은 방정식을 로 고쳐 써도 모양입니다. 이쪽은 망가집니다.
>>> def gbad(cd):
... return cd + f(cd)
>>> x = 1.0
>>> for k in range(1, 3):
... x = gbad(x)
... print(k, round(x, 4))
1 1.8342
2 -0.1554
>>> round(1 + fp(root), 2)
np.float64(-3.23)차이는 근에서의 기울기 입니다. 근 근처에서 오차는 걸음마다 배가 됩니다. 그래서 이면 수렴, 1보다 크면 발산합니다. gfix는 기울기가 0.0062라 오차가 걸음마다 1/160로 줄고, gbad는 −3.23이라 3배씩 커지며 부호까지 뒤집힙니다. 수렴하더라도 보통은 배씩 줄어드는 1차 수렴입니다. 뉴턴법은 근에서 이 되도록 만든 특별한 고정점 반복이라 2차로 빠릅니다.
같은 낙하산 문제를 상대 오차 어림 까지 풀었을 때의 기록입니다. 이분법과 가위치법은 4교시의 함수로 에서, 뉴턴법은 , 할선법은 에서 출발했습니다. 함수 계산 수는 지난 값을 기억해 아끼며 짰을 때의 수입니다.
| 방법 | 반복 횟수 | 함수 계산 수 | 최종 상대 오차 | 근을 놓치지 않나 |
|---|---|---|---|---|
| 이분법 | 34 | 36 | 3.7e-11 | 늘 |
| 가위치법 | 29 | 31 | 4.7e-11 | 늘 (느릴 수 있음) |
| 뉴턴법 | 6 | 12 ( 6, 6) | 1.1e-15 | 출발점에 달림 |
| 할선법 | 7 | 8 | 7.6e-16 | 출발점에 달림 |
열린 방법은 함수를 3~4배 적게 계산하고도 오차가 만 배 넘게 작습니다. 그런데 구간법만 "늘 찾는다"고 약속합니다. 둘의 장점을 합칠 수 없을까요? 구간 를 들고 다니면서 뉴턴 걸음을 먼저 시도하고, 그 걸음이 구간 밖으로 나가면 이분법 한 걸음으로 바꾸면 됩니다.
>>> def safe_newton(f, fp, a, b, es=1e-10, maxit=100):
... x = b
... for k in range(1, maxit + 1):
... if np.sign(f(x)) == np.sign(f(a)):
... a = x
... else:
... b = x
... x_new = x - f(x) / fp(x)
... if not a < x_new < b:
... x_new = (a + b) / 2
... if abs(x_new - x) < es * abs(x_new):
... return x_new, k
... x = x_new
... return x, maxit
>>> safe_newton(f, fp, 0.05, 3.0)
(np.float64(1.1753511424325649), 6)일부러 나쁜 출발점 3에서 시작했습니다. 그냥 뉴턴법이면 첫 걸음이 로 떨어져 멈췄을 것입니다. safe_newton은 그 걸음이 구간 밖이라 버리고 가운데 1.525로 갑니다. 그다음부터는 뉴턴 걸음이 구간 안에 떨어지니 그대로 써서, 모두 6번 만에 끝납니다. 실무 라이브러리의 브렌트 방법(Brent's method) 이 이 생각을 다듬은 것입니다. 이분법, 할선법, 2차 곡선 보간을 섞어서 도함수 없이도 빠르고 절대 구간을 벗어나지 않습니다.
상자 300개를 한꺼번에. 마지막으로 민재 팀장의 문제입니다. 할선법의 식은 더하기, 곱하기, 나누기뿐이니 NumPy 배열에 그대로 쓰면 300개 방정식을 한 번에 풉니다. 출발점은 오 박사의 조언대로 입니다.
>>> rng = np.random.default_rng(5)
>>> mass = rng.uniform(4, 20, 300)
>>> v_ok = rng.choice([8.0, 10.0, 12.0], 300)
>>> mass[:3].round(2), v_ok[:3]
(array([16.88, 16.93, 12.25]), array([10., 10., 12.]))
>>> def F(cd):
... return np.sqrt(g * mass / cd) * np.tanh(np.sqrt(g * cd / mass) * 4) - v_ok
>>> x0 = g * mass / v_ok**2
>>> x1 = 1.05 * x0
>>> for k in range(1, 5):
... x0, x1 = x1, x1 - F(x1) * (x1 - x0) / (F(x1) - F(x0))
... print(k, f"{np.abs(F(x1)).max():.1e}")
1 1.4e-03
2 5.6e-05
3 9.6e-09
4 6.8e-14
>>> np.round(x1[:3], 4)
array([1.6533, 1.6579, 0.8293])네 걸음 만에 300개 모두 속도 오차가 m/s 아래입니다. 배열 계산 네 번이면 끝나니 허브 컴퓨터에서도 눈 깜짝할 새입니다. 좋은 출발점과 빠른 방법이 만나면 이렇게 됩니다.
| 책에 적힌 모양 | 읽는 법 | 뜻 | 파이썬에서는 |
|---|---|---|---|
| 에프 프라임 엑스 | 의 도함수, 접선의 기울기 | fp(x) (식으로 직접) | |
| 뉴턴 반복 | 접선의 x절편으로 가기 | x = x - f(x) / fp(x) | |
| 엑스 스타 | 참 근 | root | |
| 이 케이 | 번째 걸음의 오차 | abs(x - root) | |
| 2차 수렴 | 오차가 제곱으로 줄어듦 | e[1:] / e[:-1]**2 | |
| 고정점 방정식 | 넣은 값이 그대로 나오는 점 | x = g(x) 반복 | |
| 하이퍼볼릭 시컨트 | 1 / np.cosh(x) |
SciPy의 optimize에 오늘 배운 방법이 모두 있습니다(이 사이트 서버에는 SciPy가 없어 이름과 쓰임만).
from scipy import optimize
optimize.newton(f, x0=0.5, fprime=fp) # 뉴턴법
optimize.newton(f, x0=0.5) # fprime 없이 부르면 할선법
optimize.newton(F, x0=g * mass / v_ok**2) # x0이 배열이면 300개를 한 번에
optimize.brentq(f, 0.5, 2.0) # 브렌트 방법: 구간이 있으면 이것부터구간을 정할 수 있으면 brentq를 씁니다. 근을 놓치지 않으면서 할선법만큼 빠릅니다. 구간이 없고 좋은 출발점이 있으며 같은 식을 아주 많이 풀어야 할 때(오늘처럼)는 배열을 받는 newton이 좋습니다. 어느 쪽이든 끝난 뒤 f(x)가 정말 0에 가까운지 확인하는 습관은 그대로입니다.
도함수를 틀리게 적기. 뉴턴법은 도함수가 조금 틀려도 수렴은 하는 경우가 많습니다. 대신 2차 수렴이 사라져 느려지고, 무엇이 잘못됐는지 알아채기 어렵습니다. 5-1처럼 중앙 차분과 한 번 비교하세요.
반복 횟수 상한 없이 돌리기. 5-3의 진동처럼 영원히 끝나지 않을 수 있습니다. maxit를 두고, 상한에 걸려 끝났는지 따로 확인하세요.
|Δx|가 작다고 근이라고 믿기. 평평한 곳에서는 가 조금만 움직여도 는 크게 남아 있을 수 있습니다. 멈춘 뒤 도 보세요.
할선법에 같은 두 점 넣기. 이면 이라 0으로 나눕니다. 실습대에서 x₀와 x₁을 겹쳐 보면 "직선이 x축과 나란하다"는 문구가 나옵니다.
fp도 바꿔야 할까요?>>> def f9(cd):
... return np.sqrt(g * m / cd) * np.tanh(np.sqrt(g * cd / m) * 4) - 9
>>> newton(f9, fp, 1.0)
(np.float64(1.452381687815308), 6)
>>> secant(f9, 1.0, 1.2)
(np.float64(1.4523816878153073), 6)
>>> path(c3, c3p, -1.2, 5)
[-1.2, -3.3758, -2.4991, -2.0472, -1.897, -1.8796]
>>> x = 1.0
>>> for k in range(1, 5):
... x = np.cos(x)
... print(k, round(x, 4))
1 0.5403
2 0.8576
3 0.6543
4 0.7935
>>> round(-np.sin(0.739085), 3)
np.float64(-0.674)1번은 입니다. 목표 속도 9는 에서 빼는 상수일 뿐이라 도함수에는 영향이 없습니다. 그래서 fp를 그대로 썼습니다. 둘 다 6번 만에 끝났습니다.
2번은 −1.2가 왼쪽 언덕(기울기 0인 ) 근처라 접선이 왼쪽 멀리 −3.38로 보냅니다. 거기서부터는 곡선이 가팔라 −1.8794로 차근차근 다가갑니다. 가까운 근 0.3473으로 가지 않는다는 점이 5-3의 '다른 근으로'입니다.
3번은 근(0.739)에서 이고 이라 수렴합니다. 다만 걸음마다 오차가 0.67배밖에 줄지 않는 1차 수렴이고, 부호가 음수라 근의 양쪽을 번갈아 오갑니다(0.54, 0.86, 0.65, 0.79). 오차를 아래로 줄이려면 55번이 걸립니다.
오 박사의 계산 노트
뉴턴-랩슨: x ← x − f(x)/f′(x). 접선이 x축과 만나는 곳으로 간다. 근 가까이에서 2차 수렴(맞는 자릿수가 두 배씩).
실패: 평평한 곳(f′ ≈ 0), 진동, 발산, 엉뚱한 근, 정의역 밖(nan). 출발점이 성패를 가른다. 물리로 좋은 출발점을 찾는다.
할선법: f′ 대신 최근 두 점의 기울기. 도함수가 필요 없고 수렴 차수 1.618, 계산 한 번당으로는 뉴턴보다 빠를 때가 많다.
고정점 반복 x ← g(x): 근에서 |g′| < 1이면 수렴, 보통 1차. 같은 방정식도 고쳐 쓰는 방법에 따라 수렴·발산이 갈린다.
안전한 혼합: 구간을 지키며 빠른 걸음을 시도, 밖으로 나가면 이분법(브렌트 방법, scipy.optimize.brentq).
같은 식을 여러 번 풀 때는 배열로 한꺼번에: 할선법·뉴턴법 식이 NumPy 배열에 그대로 쓰인다.
이틀 동안 낙하산 크기를 정한 서윤에게 민재 팀장이 새 질문을 던집니다. "근데 드론은 빨리 날수록 바람 저항으로 배터리를 많이 먹고, 천천히 날면 떠 있는 데 배터리를 먹잖아요. 400 Wh 배터리로 가장 멀리 가려면 몇 m/s로 날아야 해요?" 다음 교시에는 근 대신 꼭대기를 찾습니다. 기울기가 0인 곳을 찾는 일이라 오늘 배운 근 찾기와도 이어집니다.
새 교시·새 강의 소식 받기
이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.
R
ReflexKR
눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.
ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.
© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
질문과 답변
이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.