N. Dereceden Polinomun Kökleri : Bairstow’s Method

Her şey 2 hafta önce girdiğim “Sayısal Çözüm Yöntemleri” dersinde hocamın “n. dereceden bir polinomun tüm köklerini bulacağız.” demesiyle başladı. Şimdi bu cümlede 3 tane gizli ayrıntı var. Bunlardan ilki bu işleri tamamen nümerik tekniklerle yapacak olmamız. Bu ilk adım pek kafamızı karıştırmıyor çünkü aklımıza ilk gelen yöntem, bir epsilon hatasına kadar döngüyü tekrarlamak.

İkincisi bu kökler içinde sanal köklerinde barınacağı. Bu bizim için sorun değil bunun sebebi kompleks köklerin formülünü biliyor olmamız. Gördüğünüz gibi hala korkacak bir şey yok!

Üçüncüsü ve benim kafam en çok meşgul eden ise bu polinomun derecesinin sabit değil n. dereceden olması! Evet bu cümlenin arkasında yatan zorluğun ne olduğunu şimdi rahatça görebiliyoruz dimi? Bazı dereceleri hesaplamak kolay. Mesela 1, 2, 3 hadi 4 olsun. Peki 10, 15 hatta 50. derece olduğunu düşünün. Kısacası bize n. dereceden bir polinomun tüm köklerini bulan çok güçlü bir algoritma gerekiyor. Merak etmeyin bunu yapan bir matematiksel modele sahibiz. Bu modeli geliştiren Leonard Bairstow’a bir teşekkürü borç biliriz. O halde hazırsanız Bay Bairstow’un bu ulvi matematiksel modelini biraz olsun inceleyerek anlamaya çalışalım. Hatta bunu yazılımsal hale getirip işleri daha kolaylaştıralım!

Öncelikle tekniğin felsefesinden bahsedecek olursak n. dereceden bir polinomun 2. dereceden bir polinoma bölünmesiyle ana polinomun derecesinin düşürülmesine dayanıyor. Bu bölme işlemi devam edilerek köklerin bölünmesi daha kolay bir hal alıyor. Bu size polinom bölmesini hatırlatmalı. Burada bölme işini uzun bölme olarak değil sentetik bölme olarak yapıyoruz. Bu bölme işleminde bir kalan olabileceğini de unutmayın. Temel matematik yapısı aşağıdaki gibi.

Burada h(x) kalan olmaktadır. Katsayılar arasındaki bağıntı ise şu şekildedir.

Bu adımlardan sonra delta r ve delta s değerleri bize her döngüde yeni bir girdi verecek ve köklere yakınsamayı sağlayacak.

Önemli Not : İlk girilen r ve s değerleri tamamen keyfidir. Bu sizin kökleri bulma işinizi belki kolaylaştırabilir. Eğer ilk gerçek r ve s değerlerine yakın bir değer tahmininde bulunup bu değerleri yazarsanız, ilk kökleri elde etme süreniz kısalabilir. Bu durum tamamen şansınıza kalmıştır. Bu yüzden r ve s değerlerinin başlangıç değerlerine kafa yormaya gerek yoktur. Bazı kaynaklar bu r ve s değerlerini a dizisinden eleman seçerek başlatır. Bu durumunda keyfi olduğunu unutmayın.

Bu adımdan sonra r ve s değerlerini ne kadar değiştireceğimizi elde ediyoruz. Bu işlemi belirlediğimiz bir “Epsilon” hata payına kadar yaparak kökleri elde edebiliriz. Bence daha fazla matematiğe girmeden yazılım kısmına geçelim. Eğer bu işin arkasındaki matematiğe girmek istiyorsanız internette bulabileceğiniz güzel makaleler ver. Bu metodun arkasında yatan matematik, içerdiği Taylor açılımına ve Kısmi Türeve bakacak olursak pek küçük değil. Ben bu işlemleri hem Python hemde Matlab ortamında gerçekleştirdim ve sonuçları Matlab’ın kendi “roots” fonksiyonu ile karşılaştırdım. Aşağıda hazırladığım basit bir Python programını görebilirsiniz.

import math
import cmath

def GetRoots(a, r, s, err):
    n = (len(a) - 1)

    roots = [0]*n
    rootIndex = 0

    b = [0]*(len(a))
    c = [0]*(len(a))

    b[n] = a[n]
    b[n - 1] = a[n - 1] + r * b[n]

    c[n] = b[n]
    c[n - 1] = b[n - 1] + r * c[n]

    while n > 2:
        ep = 1
        c[n] = b[n] = a[n]
        while (ep > err):
            b[n - 1] = a[n - 1] + r * b[n]
            c[n - 1] = b[n - 1] + r * c[n]

            for i in range(n - 2, -1, -1):
                b[i] = a[i] + r * b[i + 1] + s * b[i + 2]
                c[i] = b[i] + r * c[i + 1] + s * c[i + 2]

            b[0] = a[0] + r * b[1] + s * b[2]

            delta_r = (b[0] * c[3] - b[1] * c[2]) / (c[2] * c[2] - c[1] * c[3])
            delta_s = (c[1] * b[1] - c[2] * b[0]) / (c[2] * c[2] - c[1] * c[3])

            r += delta_r
            s += delta_s

            ep = math.sqrt(delta_r * delta_r + delta_s * delta_s)

        roots[rootIndex] = (r + cmath.sqrt(r * r + 4 * s)) / 2
        roots[rootIndex + 1] = (r - cmath.sqrt(r * r + 4 * s)) / 2

        rootIndex += 2

        n -= 2

        for i in range(0, n + 1):
            a[i] = b[i + 2]

    if(n == 2):
        r = -a[1] / a[2]
        s = -a[0] / a[2]

        roots[rootIndex] = (r + cmath.sqrt(r * r + 4 * s)) / 2
        roots[rootIndex + 1] = (r - cmath.sqrt(r * r + 4 * s)) / 2

    if(n == 1):
        roots[rootIndex] = -a[0] / a[1]

    return (roots)



Her dereceye uygun olsun diye yazılıma 2. ve 1. dereceleri hesaplama özelliğini de ekledim. Bu metot şu şekilde kullanılabilir. Polinom katsayılarının 0’dan n’ye gittiğine dikkat edin.

import BairStow

# (4+x^0) + (-10*x^1) + (10*x^2) + (-5*x^3) + (1*x^4)
coeffs = [4, -10, 10, -5, 1]
r = 0.5
s = -0.5
epsilon = 10e-7

myRoots = BairStow.GetRoots(coeffs, r, s, epsilon)

for i in range(0, len(myRoots)):
    print("Root {:d} : {:.4f}".format((i + 1), (complex(myRoots[i]))))
Root 1 : 2.0000+0.0000j
Root 2 : 1.0000+0.0000j
Root 3 : 1.0000+1.0000j
Root 4 : 1.0000-1.0000j

 

Gördüğünüz üzere artık elimizde N. dereceden bir polinomun tüm köklerini hesaplayan bir algoritma var. Hemde kompleks kökleri bile hesaplayabiliyor! Tabi siz dünyalılar bunu “Matlab varken neden bu algoritma ile uğraştık?” şeklinde sorgulayabilirsiniz. Belki bir gün bunu anlatan bir yazı yazarım.

Kim bilir, belki yarın, belki yarından da yakın.

Projeye Github hesabımdan ulaşabilirsiniz.

Esen kalın.

You may also like...

Bir yanıt yazın