2. ve 3. dereceden 2 bilinmeyenli denklemler

Herkese merhaba,

Daha önce sevgili @coderistan , Sayısal Yöntemler dersinde anlatılan yöntemlerle ilgili bir görsel paylaşmıştı.

Görselde yer alan yöntemlerden ilki olan Newton-Raphson yöntemi ile ilgili bir örnek yapalım isterim.

Bu yöntem, lineer olmayan bir f(x) fonksiyonunun 0’a eşit olduğu yere, Taylor Serilerini kullanarak yakınsamaya çalışan bir yöntemdir.

Yöntem şöyle çalışıyor:

f(x)'in 0’a yaklaşabilmesi için bir başlangıç değeri seçiyoruz. Başlangıç değeri complex tipte bir sayı. Neden complex? Çünkü, denklemin reel kökleri olmayabilir.

Burada, her başlangıç değeri, f(x) = 0’a yaklaşmayı sağlamayabilir. Başlangıç değerlerini öyle bir şekilde seçmemiz lazım ki, fonksiyonun kökleri daha isabetli bir şekilde hesaplanabilsin.

Kompleks sayılar için en uygun başlangıç değerleri, bu sayılar bir çemberin kökleriyle ilgili oldukları için; çember üzerinde eşit bir şekilde dağılmış noktalar olarak seçilir. Bu seçimde, elbette cos ve sin fonksiyonlarını kullanmamız gerekiyor.

Kök sayısı, denklemin derece sayısına bağlı bir kavramdır. 3. dereceden denklemin toplamda 3 kökü, 4 katsayısı vardır.

Yani eğer denklemde 3 kök varsa, bu kökleri bulmak için başlangıç değerleri şöyle oluşturulabilir:

from math import pi, cos, sin

n = 3
roots = [complex(cos(2 * pi * i / n), sin(2 * pi * i / n) for i in range(n)]
print(roots)

Newton-Rhapson yöntemi, f(x) fonksiyonunun mevcut eğimi boyunca sürekli türevinin alınarak, türevin 0’a yaklaştığı yeri bulmaya çalışır.

x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}

Türev fonksiyonunu şöyle yazabiliriz:

def derivate(
    x: complex,
    n: int,
    coef: list[float],
    h: float = 1e-5
) -> complex:
    return (func(x + h, n, coef) - func(x, n, coef)) / h

Newton-Rhapson yöntemi ise şöyle yazılabilir:

def newton_rhapson_method(
    n: int,
    coef: list[float],
    roots: list[complex],
    max_step: int,
    tolerance: float
) -> None:
    for i in range(n):
        x = complex(cos(2 * pi * i / n), sin(2 * pi * i / n))
        for step in range(max_step):
            x = x - func(x, n, coef) / derivate(x, n, coef)
            if abs(func(x, n, coef)) < tolerance:
                break
        roots[i] = x

Bu fonksiyonları oluşturduktan sonra, katsayılara göre değişecek olan denklem fonksiyonunu tanımlayalım:

def func(x: complex, n: int, coef: list[float]) -> complex:
    total = complex(0, 0)
    for i in range(n + 1):
        total += coef[i] * x ** (n - i)
    return total

Buradaki func fonksiyonu, köklerini bulmaya çalıştığımız fonksiyonu üretir ve bu fonksiyonu karmaşık tipte olan x değerleri ile çalıştırır.

Şimdi, main fonksiyonunu tanımlayabiliriz:

def main():
    coef = [1, -1, -1]
    n = len(coef) - 1
    roots = [complex(0, 0)] * n
    newton_rhapson_method(n, coef, roots, 100, 1e-7)
    for i in range(n):
        print(f"{roots[i].real} + {roots[i].imag}j")


if __name__ == "__main__":
    main()

Yukarıda coef isimli değişken, 2. dereceden bir denklem olan Fibonacci Serisi fonksiyonunun katsayıları olan 1, -1, -1 değelerini içeren katsayı listesidir.

Fibonacci fonksiyonunun denklemi::

f(x) = x^2 -x - 1

Gördüğünüz gibi, bu denklemin katsayıları [1, -1, -1].

Fibonacci fonksiyonu ile alakalı olarak aşağıdaki başlığı da incelemenizi tavsiye ederim:

Yukarıdaki başlıkta fibonacci fonksiyonunun kökleri şöyle bulunmuştu:

kok1 = 1.618033988749895
kok2 = -0.6180339887498949

Yukarıdaki Newton-Rhapson metodu ise kökleri aşağıdaki gibi bulur:

kok1 = 1.618033988752065 + 0.0j
kok2 = -0.6180339887479553 + -4.067290634408824e-27j

Ondalık kısımda farklılıklar olması beklenen bir durumdur.

Newton-Raphson yöntemi, bir fonksiyonun kökünü bulmak için, başlangıç değeri kullanır ve yinelemeli (iteratif) bir yaklaşım yapılır. Bu yaklaşım, her adımda fonksiyonun türevini kullanarak köke daha yakın bir noktaya yaklaşılmasını sağlar.

Diğer yandan, Durand-Kerner yönteminde, başlangıç değerleri seçilmez; polinomun katsayıları kullanılarak bir companion matrix (kompanyon/refakatçı matris) oluşturulur. Bu matrisin özdeğerleri hesaplanarak, polinomun kökleri bulunur. Özdeğerler, matrisin karakteristik özelliklerini gösteren ve polinomun köklerine karşılık gelen değerlerdir. Bu yaklaşımda, kökler doğrudan kompanyon matrisin özdeğerlerinden elde edilir.

Durand-Kerner yöntemi, Newton-Raphson yöntemine kıyasla, özellikle polinomların köklerini aynı anda ve daha isabetli bir şekilde bulması açısından avantajlıdır.

Durand-Kerner yöntemi ile alakalı olarak aşağıdaki linkleri de inceleyebilirsiniz:

Durand-Kerner Yöntemi:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-


def dot_product(n: int, v1: list[complex], v2: list[complex]) -> complex:
    result = complex(0, 0)
    for i in range(n):
        result += v1[i] * v2[i]
    return result


def scalar_multiply(
    n: int,
    scalar: complex,
    vector: list[complex],
    multiplied: list[complex]
) -> None:
    for i in range(n):
        multiplied[i] = scalar * vector[i]


def vector_subtract(
    n: int,
    v1: list[complex],
    v2: list[complex],
    subtracted: list[complex]
) -> None:
    for i in range(n):
        subtracted[i] = v1[i] - v2[i]


def gram_schmidt(
    n: int,
    A: list[list[complex]],
    Q: list[list[complex]],
    R: list[list[complex]],
) -> None:
    for i in range(n):
        v = [complex(0, 0)] * n
        for j in range(n):
            v[j] = A[j][i]
        for j in range(i):
            R[j][i] = dot_product(n, Q[j], v)
            multiplied = [complex(0, 0)] * n
            scalar_multiply(n, R[j][i], Q[j], multiplied)
            subtracted = [complex(0, 0)] * n
            vector_subtract(n, v, multiplied, subtracted)
            for k in range(n):
                v[k] = subtracted[k]
        R[i][i] = dot_product(n, v, v) ** .5
        for j in range(n):
            Q[i][j] = v[j] / R[i][i]


def qr_algorithm(
    n: int,
    roots: list[complex],
    companion: list[list[float]],
    A: list[list[complex]],
    Q: list[list[complex]],
    R: list[list[complex]],
    max_step: int,
    tolerance: float
) -> None:
    for i in range(n):
        for j in range(n):
            A[i][j] = complex(0, companion[i][j])
    for step in range(max_step):
        for i in range(n):
            for j in range(n):
                Q[i][j] = complex(0, 0)
                R[i][j] = complex(0, 0)
        gram_schmidt(n, A, Q, R)
        for i in range(n):
            for j in range(n):
                total = complex(0, 0)
                for k in range(n):
                    total += R[i][k] * Q[k][j]
                A[i][j] = total
        converged = 1
        for i in range(1, n):
            for j in range(i):
                if abs(A[i][j]) >= tolerance:
                    converged = 0
                    break
            if not converged:
                break
        if converged:
            break
    for i in range(n):
        roots[i] = A[i][i]


def companion_matrix(
    n: int,
    coef: list[float],
    companion: list[list[float]]
) -> None:
    for i in range(n):
        for j in range(n):
            if not j:
                companion[i][j] = -1.0 * coef[i + 1] / coef[0]
            elif i + 1 == j:
                companion[i][j] = 1


def func(x: complex, n: int, coef: list[float]) -> complex:
    total = complex(0, 0)
    for i in range(n + 1):
        total += coef[i] * x ** (n - i)
    return total


def durand_kerner_method(
    n: int,
    coef: list[float],
    roots: list[complex],
    max_step: int,
    tolerance: float
):
    companion = [[0] * n for _ in range(n)]
    companion_matrix(n, coef, companion)
    A = [[complex(0, 0)] * n for _ in range(n)]
    Q = [[complex(0, 0)] * n for _ in range(n)]
    R = [[complex(0, 0)] * n for _ in range(n)]
    qr_algorithm(n, roots, companion, A, Q, R, max_step, tolerance)
    for i in range(n):
        for step in range(max_step):
            denominator = complex(1, 0)
            for j in range(n):
                if i != j:
                    denominator *= roots[i] - roots[j]
            roots[i] = roots[i] - func(roots[i], n, coef) / denominator
            if abs(func(roots[i], n, coef)) < tolerance:
                break


def main():
    coef = [1, -1, -1]
    n = len(coef) - 1
    roots = [complex(0, 0)] * n
    durand_kerner_method(n, coef, roots, 100, 1e-7)
    for i in range(n):
        print(f"{roots[i].real} + {roots[i].imag}j")


if __name__ == "__main__":
    main()
1 Beğeni