[Python]비선형 방정식 - 이분법, 뉴턴법, 할선법

비선형 방정식

  • 2차 이상 방정식들을 의미(곡선)


이분법

이분법 Bisection Method

  • 연속함수의 속성을 활용

  • 각 단계에서 구간 [𝑎, 𝑏]와 𝑢 = 𝑓(𝑎) , 𝑣 = 𝑓(𝑏)의 값이 주어진다.

    • 이때 𝑢와 𝑣는 𝑢𝑣 < 0을 만족(부호가 다르다는 것)
  • 이 폐구간의 중점인 \(𝑐 = \frac{1}{2}(a+b)\) 를 정하고, 𝑤 = 𝑓(𝑐)를 구한다.

  • 우연히 𝑓(𝑐)=0이라면 알고리즘 종료 후 값 반환

  • 보통은 𝑤 ≠ 0, 𝑤𝑢 < 0 || 𝑤𝑣 < 0

  • if 𝑤𝑢 < 0 ?

    • 𝑓의 근이 구간 [𝑎, 𝑐]에 존재함

    • 구간을 축소하여 계속 진행

  • If 𝑤𝑢 > 0 ?

  • 𝑓의 근이 구간 𝑎, 𝑐 사이에 존재하는지의 여부를 알 수 없음.

  • 그러나 𝑤𝑣 < 0이므로 [𝑐, 𝑏]에 𝑓의 근이 반드시 존재, 구간을 축소하여 계속 진행

  • 구간의 크기가 충분히 줄어들 때까지 반복 (ex) \(|𝑏 − 𝑎| < \frac{1}{2} * 10^{-6}\)

    • 오차 범위 이내여야 한다고 보면 됨
  • 이때 가장 합리적인 근의 추정치 : \(\frac{a+b}{2}\)

이분법의 수렴성 분석

  • 𝑓가 \([𝑎_0, 𝑏_0]\)의 양 끝에서 서로 다른 부호의 함숫값을 갖는 연속함수.

  • \([𝑎_0, 𝑏_0]\) 내에 한 근 𝑟이 존재

  • \(c_0 = \frac{𝑎_0+𝑏_0}{2}\)를 근 r의 근삿값으로 잡으면 \(|r - c_0| ≤ \frac{b_0 - a_0}{2}\)

import numpy as np
import sympy as sp

def Bisection(f,a,b,e,nmax):
    fa = f.evalf(subs={x:a})
    fb = f.evalf(subs={x:b})
    if(fa*fb>0):
        print("f(a) and f(b) have same signs.")
        return
    err = b-a
    for i in range(1, nmax+1):
        err = err/2
        c = a + err
        fc = f.evalf(subs={x:c})
        if abs(err)<e:
            print("convergence at " +str(i) +", x = "+str((a+b)/2.0)+", f(x) = "+str(fc))
            return
        if (fa*fc<0):
            b=c
            fb=fc
        else:
            a=c
            fa=fc


뉴턴법

뉴턴법 Newton-Raphson Method

  • 미분 가능한 함수 𝑓에 대해 적용

    • 함수 𝑓가 모든 위치에서 하나의 기울기를 가짐

    • 함수 𝑓가 유일한 접선을 가짐

  • 𝑓의 그래프 위의 한 점 (𝑥0, 𝑓(𝑥0))에 접선이 존재

    • 접선은 접점 근처 함수의 곡선에 대한 좋은 근사
  • 미분 조건 : 연속함수, 좌미분계수=우미분계수(기울기가 동일)

  • 𝑓의 그래프 위의 한 점 (𝑥0, 𝑓(𝑥0))에 접선이 존재

    • 접선 : \(𝑙(𝑥) = 𝑓′(𝑥_0)(𝑥 − 𝑥_0) + 𝑓(𝑥_0)\)
  • 𝑓의 근에 대한 근사로 𝑙의 근을 선택

  • l의 근 : \(x_1 = x_0 - \frac{f(x_0)}{f’(x_0)} \), \(x_2 = x_1 - \frac{f(x_1)}{f’(x_1)} $ , $ x_3 = x_2 - \frac{f(x_2)}{f’(x_2)} …\)

  • 이러한 점들의 수열은 𝑓의 근으로 수렴

뉴턴법 - 초기값 설정(나쁜 초기값)

  • 발산, 평평한 점, 순환 그래프는 나쁜 초기값을 초래한다.

  • 적절한 초기값을 설정해야 뉴턴법이 정상 동작한다.

  • 이분법을 적용하여 근이 존재할 범위 파악 후 적절한 초기값을 설정

import numpy as np
import sympy as sp
def Newton(f, fprime, a, nmax, eps, delta):
    fx = f.evalf(subs={x:a})
    for i in range(1, nmax+1):
        fp = fprime.evalf(subs={x:a})
        if(abs(fp)<delta):
            print("small derivative")
            return
        d = fx/fp
        a = a - d
        fx = f.evalf(subs={x:a})
        if(abs(d)<eps):
            print("convergence at "+str(i)+", x = "+str(a)+", f(x) = "+str(fx))
            return
x=sp.symbols("x")
f=21*x**3 - 150*x**2 + 41*x - 220


이분법과 뉴턴법 코드 비교

x = sp.symbols("x")
f = x**3 + 2*x**2 + 10*x + 23
fprime = 3*x**2 + 4*x + 10
print("Bisection") # 이분법
Bisection(f,-10,10,1E-7,100)
print("Newton") # 뉴턴법
Newton(f,fprime,0,100,1E-7,1E-7)
# 뉴턴은 5번, 이분법은 28번 반복했음. 뉴턴이 훨씬 빠름.
# f(x)는 뉴턴이 훨씬 작으니까 정밀도도 훨씬 높음.
Bisection
convergence at 28, x = -2.202034369111061, f(x) = 6.71329234040575e-7
Newton
convergence at 5, x = -2.20203441176566, f(x) = -1.37349444519054e-15


할선법

할선법 Method

  • 뉴턴법만큼 빠르게 근으로 수렴하면서, 쉽게 계산하는 방법

    • 뉴턴법 : \(x_{n+1} = x_n - \frac{f(x_n)}{f’(x_n)}\)

    • \(𝑓′(𝑥_𝑛)\) 을 쉽게 계산할 수 있는 근삿값으로 교체

  • 할선법 : \(x_{n+1} = x_n - (\frac{x_n-x_{n-1}}{f(x_n)-f(x_{n-1})}) f(x_n) \)

import numpy as np
import sympy as sp

def swap(a, b):
    temp = a
    a = b
    b = temp
    return a, b

def Secant(f, a, b, nmax, eps):
    fa = f.evalf(subs={x:a}) # f(a)
    fb = f.evalf(subs={x:b}) # f(b)
    if abs(fa) > abs(fb):
        a, b = swap(a, b)
        fa, fb = swap(fa, fb)
    for i in range(2, nmax+1):
        if abs(fa) > abs(fb):
            a, b = swap(a, b)
            fa, fb = swap(fa, fb)
        d = (b-a)/(fb-fa)
        b = a
        fb = fa
        d = d*fa # a-d 하기전에 eps와 비교위해(오차범위 확인)
        if abs(d) < eps:
            print("convergence at " +str(i) +", x = "+str(a)+", f(x) = "+str(fa))
            return
        a = a-d # 다음 x값을 구한것임.
        fa = f.evalf(subs={x:a}) # f(a)


이분법, 뉴턴법, 할선법 전체 비교

x = sp.symbols("x")
# 식 1 : f=21*x**3-150*x**2+41*x-220
# 식 2 : f=22*x**3-25*x**2+21*x-100
f1 = 21*x**3-150*x**2+41*x-220
fprime1 = 63*x**2-300*x+41
f2 = 22*x**3-25*x**2+21*x-100
fprime2 = 66*x**2-50*x+21
print("Bisection") # 이분법
Bisection(f1,-10,10,1E-7,100)
Bisection(f2,-10,10,1E-7,100)
print("Newton") # 뉴턴법
Newton(f1,fprime1,0,100,1E-7,1E-7)
Newton(f2,fprime2,0,100,1E-7,1E-7)
print("Secant") # 할선법
Secant(f1, -10, 10, 100, 1E-7)
Secant(f2, 0, 1, 100, 1E-7)
Bisection
convergence at 28, x = 7.07616962492466, f(x) = -1.79966109081769e-5
convergence at 28, x = 1.8966569751501083, f(x) = -4.03194505697596e-6
Newton
convergence at 8, x = 7.07616964170165, f(x) = -1.16819010758179e-13
convergence at 8, x = 1.89665699979684, f(x) = -8.21520068727071e-15
Secant
convergence at 7, x = 7.07616964172657, f(x) = 2.67377372820549e-8
convergence at 12, x = 1.89665702055653, f(x) = 3.39606631760622e-6

댓글남기기