Çarpım Tabloları, Mandelbrot ve Matematiğin Kalbi

Merhabalar,

Bir kaç gün önce karşıma çıkan, Mathologer kullanıcı adıyla YouTube’da yayın yapan Burkard Polster’a ait bir videodan bahsetmek istiyorum.

İlgili arkadaşlar için video adresi: https://www.youtube.com/watch?v=qhbuKbxJsk8

Bu videoda Burkard Polster, çarpım tablolarını kullanarak nasıl belli başlı fractalların oluştuğunu bizlere animasyon üzerinden anlatıyor. Animasyonu izlerken, animasyonu tasarlama isteği oluştu ve aşağıda paylaştığım kodlar ortaya çıktı.

Animasyon bir çember üzerinde gösteriliyor. Mesela aşağıdaki gibi:

Bu çember üzerinde, çemberi eşit parçalara bölen, istediğimiz kadar nokta tanımlayabiliriz değil mi?

Bir çemberi 5 eşit parçaya bölen 5 adet noktayı mesela şöyle gösterebiliriz.

Veya bir çemberi 20 eşit parçaya bölen 20 adet noktayı şöyle gösterebiliriz:

Yukarıda her bir noktanın bir sayıyla gösterildiğini görüyorsunuz.

Şimdi, çemberdeki her bir noktayı çemberdeki başka bir noktayla, bir çarpan yardımıyla bağlayacağız.

Örneğin 5 tane noktamız olsun. Ve çarpan olarak da 2’yi seçmiş olalım.

  • 1’i 2 ile çarparsak sonuç 2 olur. O halde 1’den 2’ye bir çizgi çizilir.
  • 2’yi 2 ile çarparsak sonuç 4 olur. 2’den 4’e bir çizgi çizilir.
  • 3’ü 2 ile çarparsak sonuç 6 olur. Ama 5 tane noktamız olduğu için, 1, 6’yı simgeler. O halde 3’den 1’e bir çizgi çizilir.
  • 4’ü 2 ile çarparsak sonuç 8 olur. 5 tane noktamız olduğu için, 8’in 5’e bölümünden kalan 3 olur. O halde 4’den 3’e doğru bir çizgi çizilir.
  • 5’i 2 ile çarparsak sonuç 10 olur. 5 tane noktamız olduğu için, 10’un 5’e bölümünden kalan 0 olur. O halde 5’den 5’e giden bir çizgi çizilir. (5’den 5’e giden bir çizgi çizemeyiz.)

Sonuç olarak 5 nokta için çarpan 2 olursa, çizgilerin görünümü şu şekilde olacaktır:

6 nokta için çarpan 2 olursa çizgilerin görünümü şu şekilde olur:

7 nokta için çarpan 2 olursa, çizgilerin görünümü şu şekilde olur:

25 nokta için çarpan 2 olursa, çizgilerin görünümü şu şekilde olur:

Dikkatinizi çektiyse, nokta sayımızı artırdıkça özel bir şekil oluşmaya başlıyor. Şekil henüz çok belirgin değil. Nokta sayımızı biraz daha artıralım, mesela 100’e çıkartalım:

360’a çıkartalım:

İşte, çarpanın 2 olduğu durumda oluşan bu geometrik şekle “Cardioid” deniyor.

Çarpanın 3 olduğu durumda oluşan geometrik şekle “Nephroid” deniyor.

Bu şekillerin genel ismi de episikloid’tir.

Epicycloid - Wikipedia
In geometry, an epicycloid is a plane curve produced by tracing the path of a chosen point on the circumference of a circle—called an epicycle—which rolls without slipping around a fixed circle.

Çarpan 2.5 olsaydı nasıl olurdu?

Gördüğünüz gibi yarısı Cardioid, yarısı Nephroid olan bir şekil bu.

Cardioid’in oluşumu:
a

Episikloid’lerin oluşumu:
a2

a2

a3

Kod:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk

from math import cos, sin, radians


class Point(list):
    def __init__(self, x: float, y: float, r: float, q: float):
        super().__init__(
            j + (i * f(radians(q)))
            for i in [r, r + 5] for f, j in ([cos, x], [sin, y])
        )

    def xy(self):
        return (self[0] + self[2]) / 2, (self[1] + self[3]) / 2


class Circle(list):
    def __init__(self, x: float, y: float, r: float):
        super().__init__([x - r, y - r, x + r, y + r])


class Canvas(tk.Canvas):
    points = []

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.pack()
        self.x = pow(2, 8)
        self.y = pow(2, 8)
        self.r = 150
        self["width"] = self.x * 2
        self["height"] = self.y * 2
        self.create_oval(Circle(x=self.x, y=self.y, r=self.r))

    def create_points(self, n: int, x: int | float):
        for i in range(1, n + 1):
            p1 = Point(x=self.x, y=self.y, r=self.r, q=i * 360 / n)
            p2 = Point(x=self.x, y=self.y, r=self.r, q=((x * i) % n) * 360 / n)
            p3 = Point(x=self.x, y=self.y, r=self.r + 10, q=i * 360 / n)
            self.points += [self.create_oval(p1, fill="black")]
            self.points += [self.create_line(*p1.xy(), *p2.xy())]
            self.points += [self.create_text(*p3.xy(), text=i)]

    def delete_points(self):
        self.delete(self.points)
        self.points = []

    def change_points(self, n: int, x: int | float):
        self.delete_points()
        self.create_points(n, x)


class Spinbox(tk.Frame):
    def __init__(
            self,
            text: str,
            start: int,
            end: int,
            step: float,
            var: tk.IntVar | tk.DoubleVar,
            *args,
            **kwargs
    ):
        super().__init__(*args, **kwargs)
        self.pack(side="left")
        self.var = var
        self.label = tk.Label(master=self, text=text, font="Default 9 bold")
        self.label.pack()
        self.spinbox = tk.Spinbox(
            master=self,
            from_=start,
            to=end,
            increment=step,
            textvariable=self.var,
        )
        self.spinbox.pack()


class Window(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.frame = tk.Frame(master=self, bg="white")
        self.frame.pack()
        self.number_of_points = Spinbox(
            master=self.frame,
            text="Number of Points",
            start=0,
            end=1000,
            step=1,
            var=tk.IntVar()
        )
        self.n_times = Spinbox(
            master=self.frame,
            text="N Times",
            start=1,
            end=1000,
            step=0.1,
            var=tk.DoubleVar()
        )
        self.canvas = Canvas(master=self, bg="white")
        for i in [self.number_of_points, self.n_times]:
            i.spinbox["command"] = lambda: self.canvas.change_points(
                n=self.number_of_points.var.get(),
                x=self.n_times.var.get()
            )
            i.spinbox.bind(
                sequence="<KeyRelease>",
                func=lambda event: self.canvas.change_points(
                    n=self.number_of_points.var.get(),
                    x=self.n_times.var.get()
                )
            )
        self.mainloop()


if __name__ == "__main__":
    Window()
7 Beğeni

Herşey bir varsayımla başlar…

Neden çember?

Neden başka bir çokgen değil?

Neden bir küre değil?

Neden üc boyutlu değil?

Varsayımını, yani başlangıç noktasını sorgulamadan istikameti inceleyemiyorum…

Sonlu elemanlardan oluşan varyasyonların sonlu sayıda sonuçları vardır. Neden bu kadar çemberin içine hapsolmuşlar anlamam mümkün değil.

Bunlar hep sarkaçlardan oluyor. Bir noktaya bağlandıklarında hep eşit uzaklıkta uzaklaşabileceklerini düşünüyorlar.

Aslında bu çarpmaları bir merkezden üç boyutlu ışın olarak hesaplasal farklı katılar da oluşabilir ama tabi simetri beynin hastalığıdır, simetrik değilse ilgi çekici olamaz.

Çemberlere hapsolmayalım, çok boyutlu düşünelim…

Teorik olarak, çarpma iterasyonunu her türlü çokgen üzerine uygulayabiliriz. Bunda bir engel yok.

Mandelbulb - Wikipedia
The Mandelbulb is a three-dimensional fractal, constructed for the first time in 1997 by Jules Ruis and in 2009 further developed by Daniel White and Paul Nylander using spherical coordinates.
A canonical 3-dimensional Mandelbrot set does not exist, since there is no 3-dimensional analogue of the 2-dimensional space of complex numbers. It is possible to construct Mandelbrot sets in 4 dimensions using quaternions and bicomplex numbers.

Romanesco broccoli - Wikipedia
Fractal structure
See also: Cauliflower § Fractal dimension
The inflorescence (the bud) is self-similar in character, with the branched meristems making up a logarithmic spiral, giving a form approximating a natural fractal; each bud is composed of a series of smaller buds, all arranged in yet another logarithmic spiral. This self-similar pattern continues at smaller levels. The pattern is only an approximate fractal since the pattern eventually terminates when the feature size becomes sufficiently small. The number of spirals on the head of Romanesco broccoli is a Fibonacci number.[5]

Pek anlayamadım?

The red curve is an epicycloid traced as the small circle (radius r = 1) rolls around the outside of the large circle (radius R = 3).

Bu kısmı biraz daha açarsanız sevinirim.

Galiba mandelbulb’dan bahsediyorsunuz. Gayet de ilgi çekici aslında:

Doğru söylüyorsunuz…

2 Beğeni

Evet teorik ve pratik olarak çokgenlere de uygulayabiliriz bu konuya değinmek istedim.

Yani çarptığımız sayılar ve iterasyonları sonlu ise, çarpma sonuçları da sonlu sayıdadır. Sonluluğunu kodumuzdaki limitlere göre söyleyebiliriz. Genişletsek bile sonludur. Ve sonuçları da sonludur. Sınırlı bir küme içerisinde sınırlı değerler üretilir. Sınırlı değerlerin rastgeleliği olmadığından çok da etkileyici gelmiyor bana. İçersinde rastgelelik göremeyince özgünlük ortadan kaybolup sıradanlaşıyor. Buna vurgu yaptım.

Tabi:

Reşit Öztürk on Twitter: “6,8 Büyüklüğündeki bir deprem, Olympia Washington’u sallarken, o bölgedeki bir kum sarkacı (Foucault Sarkacı) da o esnada hareke başlar. Sarsıntılar durduğunda ise, kumda bu görüntüler ortaya çıkar. (Alnt) https://t.co/P9DDU7jRsL” / Twitter

Falcaut Sarkacının üç boyutlu hareketinin, iki boyutlu yansıması. Hep bu fraktallar bana bunu hatırlatır.

Oysa ki iyi boyutlu bir yüzey yerine küresel bir yüzeyde çalışılsa o geometrik desenler o şekilde oluşmaz, küreye yayıldığını hayal edebilirsiniz.

Evet bu da filmin diğer bir boyutu. Yani Üç boyutlu fraktal hesaplamaları izdüşüm fraktallardan nispeten daha rastgele yada daha geniş bir küme yi temsil ediyor diyebiliriz yine de sonlu.

Doğru, yanlış olarak yaklaşmadım konunuza, konu güzel çok boyutlandıralım diye baktım.

Bazan farklı bakış açıları farklı yönlerde genişleme sağlar.

Çelişmek değil, katkı sağlamak olarak bakın olaya.

Bu arada bir küreyi hızlıca gerçekleyebilecek matpot alternatifi ve gl alternatifi düşünürken,

Şu çıktı karşıma;

import pyvista as pv

# Create source to ray trace
sphere = pv.Sphere(radius=0.85)

# Define line segment
start = [0, 0, 0]
stop = [0.25, 1, 0.5]

# Perform ray trace
points, ind = sphere.ray_trace(start, stop)

# Create geometry to represent ray trace
ray = pv.Line(start, stop)
intersection = pv.PolyData(points)

# Render the result
p = pv.Plotter()
p.add_mesh(sphere, show_edges=True, opacity=0.5, color="w", lighting=False, label="Test Mesh")
p.add_mesh(ray, color="blue", line_width=5, label="Ray Segment")
p.add_mesh(intersection, color="maroon", point_size=25, label="Intersection Points")
p.add_legend()
p.show()

belki hesapları görselleştirmek için denenebilir.

Az kodla çok iş çıkarması dikkatimi çekti.

Bu da bu işin python kodlama tarafına bakış açısı değişikliği olsun.

Kolay gelsin, böyle sıra dışı bakış açıları geliştiren yazılarınızın devamı diliyorum.

2 Beğeni

Parametre sayısı şu anda iki tane:

Nokta sayısı ve katlama sayısı.

Kaç kenarlı olacağını belirleyen bir parametreyi de oluşturduk diyelim.

Dört kenarlı şekil mesela bir kare mi, oluştursun yoksa bir yamuk mu? İç açıları toplamı nasıl olsun? Bu da bir parametre. Yani bu aslında noktalar arasındaki mesafeyi ayarlayan bir parametre olur.

Anladım. Etkileyicilik öznel bir konu tabi.

Kelimenin tam anlamıyla “dehşetin sanatı”. :smiley: Şakası yok.

Yok, yanlış anlamadım. Güzel bir fikir verdiniz. İterasyonun gerçekleşeceği şekli de parametrik yapabiliriz tabi. Ama 3 boyutlu uzayın matematiği şu an benim için daha anlaşılmaz seviyede. Elbette bunun için geliştirilmiş uygulamalar, arayüzler, kütüphaneler var. İşin tasarım kısmında değilim açıkçası, matematiği anlamaya çalışıyorum. Bu yüzden daha üçüncü boyutun matematiğine çok yolum var.

İlginç bir görselleştirme aracına benziyor. Aklımızda bulunsun. Teşekkürler. Size de kolay gelsin.

1 Beğeni

İlham verici ve ufuk açıcı bir konu.

2 Beğeni

Mandelbrotun kendisini belirli aralıklarla kopyalamak için 2 parametre daha ekledim.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tkinter as tk

from math import cos, sin, radians


class Point(list):
    def __init__(self, x: float, y: float, r: float, q: float):
        super().__init__(
            j + (i * f(radians(q)))
            for i in [r, r + 5] for f, j in ([cos, x], [sin, y])
        )

    def xy(self):
        return (self[0] + self[2]) / 2, (self[1] + self[3]) / 2


class Circle(list):
    def __init__(self, x: float, y: float, r: float):
        super().__init__([x - r, y - r, x + r, y + r])


class Canvas(tk.Canvas):
    points = []

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.pack()
        self.x = pow(2, 8)
        self.y = pow(2, 8)
        self.r = 225
        self["width"] = self.x * 2
        self["height"] = self.y * 2

    def create_points(self, n: int, x: int | float, c: int, d: float):
        for i in range(1, n + 1):
            for j in range(c):  # for j in range(1, c + 1)
                self.points += [
                    self.create_line(
                        *Point(x=self.x, y=self.y, r=self.r - (j * d), q=i * 360 / n).xy(),
                        *Point(x=self.x, y=self.y, r=self.r - (j * d), q=((x * i) % n) * 360 / n).xy()
                    )
                ]

    def delete_points(self):
        self.delete(*self.points)
        self.points = []

    def change_points(self, n: int, x: int | float, c: int, d: float):
        self.delete_points()
        self.create_points(n, x, c, d)


class Spinbox(tk.Frame):
    def __init__(
            self,
            text: str,
            start: int,
            end: int,
            step: float,
            var: tk.IntVar | tk.DoubleVar,
            *args,
            **kwargs
    ):
        super().__init__(*args, **kwargs)
        self.pack(side="left")
        self.var = var
        self.label = tk.Label(master=self, text=text, font="Default 9 bold")
        self.label.pack()
        self.spinbox = tk.Spinbox(
            master=self,
            from_=start,
            to=end,
            increment=step,
            textvariable=self.var,
        )
        self.spinbox.pack()


class Window(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.frame = tk.Frame(master=self, bg="white")
        self.frame.pack()
        self.number_of_points = Spinbox(
            master=self.frame,
            text="Number of Points",
            start=0,
            end=pow(10, 6),
            step=1,
            var=tk.IntVar()
        )
        self.n_times = Spinbox(
            master=self.frame,
            text="N Times",
            start=1,
            end=pow(10, 6),
            step=1,
            var=tk.IntVar()
        )
        self.copy = Spinbox(
            master=self.frame,
            text="Self Copy",
            start=1,
            end=pow(10, 6),
            step=1,
            var=tk.IntVar()
        )
        self.offset = Spinbox(
            master=self.frame,
            text="Offset",
            start=1,
            end=pow(10, 6),
            step=.1,
            var=tk.DoubleVar()
        )
        self.canvas = Canvas(master=self, bg="white")
        for i in [self.number_of_points, self.n_times, self.copy, self.offset]:
            i.spinbox["command"] = lambda: self.canvas.change_points(
                n=self.number_of_points.var.get(),
                x=self.n_times.var.get(),
                c=self.copy.var.get(),
                d=self.offset.var.get()
            )
            i.spinbox.bind(
                sequence="<KeyRelease>",
                func=lambda event: self.canvas.change_points(
                    n=self.number_of_points.var.get(),
                    x=self.n_times.var.get(),
                    c=self.copy.var.get(),
                    d=self.offset.var.get()
                )
            )
        self.mainloop()


if __name__ == "__main__":
    Window()

a1
a1
a1
a1
a1
a1
a1
a1

3 Beğeni

julia_set

Görselleştrimeye farklı boyut katalım.

Kod, gif yazıyor, yazması biraz uzun sürdü ama yazdırabildim.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

def julia_quadratic(zx, zy, cx, cy, threshold):
    """Calculates whether the number z[0] = zx + i*zy with a constant c = x + i*y
    belongs to the Julia set. In order to belong, the sequence 
    z[i + 1] = z[i]**2 + c, must not diverge after 'threshold' number of steps.
    The sequence diverges if the absolute value of z[i+1] is greater than 4.
    
    :param float zx: the x component of z[0]
    :param float zy: the y component of z[0]
    :param float cx: the x component of the constant c
    :param float cy: the y component of the constant c
    :param int threshold: the number of iterations to considered it converged
    """
    # initial conditions
    z = complex(zx, zy)
    c = complex(cx, cy)
    
    for i in range(threshold):
        z = z**2 + c
        if abs(z) > 4.:  # it diverged
            return i
        
    return threshold - 1  # it didn't diverge




x_start, y_start = -2, -2  # an interesting region starts here
width, height = 4, 4  # for 4 units up and right
density_per_unit = 200  # how many pixles per unit

# real and imaginary axis
re = np.linspace(x_start, x_start + width, width * density_per_unit )
im = np.linspace(y_start, y_start + height, height * density_per_unit)


threshold = 20  # max allowed iterations
frames = 100  # number of frames in the animation

# we represent c as c = r*cos(a) + i*r*sin(a) = r*e^{i*a}
r = 0.7885
a = np.linspace(0, 2*np.pi, frames)

fig = plt.figure(figsize=(10, 10))  # instantiate a figure to draw
ax = plt.axes()  # create an axes object

def animate(i):
    ax.clear()  # clear axes object
    ax.set_xticks([], [])  # clear x-axis ticks
    ax.set_yticks([], [])  # clear y-axis ticks
    
    X = np.empty((len(re), len(im)))  # the initial array-like image
    cx, cy = r * np.cos(a[i]), r * np.sin(a[i])  # the initial c number
    
    # iterations for the given threshold
    for i in range(len(re)):
        for j in range(len(im)):
            X[i, j] = julia_quadratic(re[i], im[j], cx, cy, threshold)
    
    img = ax.imshow(X.T, interpolation="bicubic", cmap='magma')
    return [img]

anim = animation.FuncAnimation(fig, animate, frames=frames, interval=50, blit=True)
anim.save('julia_set.gif', writer='imagemagick')

Kaynak:
Animate Your Own Fractals in Python with Matplotlib · Matplotblog

Matematiksel formüller değiştirilerek farklı fraktallar elde edilebilir.

Matplot’tan biraz daha canlı duruyor.

Bunun üzerine mi adapte etsek mi kodları. Vaktim olursa belki bakarım.

İlgilenenlere farklı bir bakış açısı getrisin diye ekledim.

3 Beğeni