Mandelbrot set

Mandelbrot set

Fraktalas yra geometrinė forma, kuriai yra būdingas panašumas į save, bei begalinis detalumas. Mandelbroto aibė yra vienas iš žinomiausių fraktalų pasaulyje, išgarsėjęs apie 1980 metus. Vėlus tokio įspūdingo objekto atsiradimas buvo nulemtas modernių kompiuterių nebuvimo.

Nors Mandelbroto aibė yra begalo (tikrąja šio žodžio prasme) komplikuota forma, ją sugeneruoti galima naudojant neįtikėtinai paprastą formulę: z = z2 + C. Čia z yra iteruojamas kompleksinis skaičius, o C – konstanta – koordinatė kompleksinėje plokštumoje.

Skaičius priklauso Mandelbroto aibei, jei z niekada “nepabėga” į begalybę, nesvarbu kiek iteracijų būtų atlikta. Kadangi mes neturime galimybių tuom įsitikinti, galima nustatyti “pakankamai aukštą” pakartojimų skaičių, ir jei jį peržengus z visdar neviršija dviejų, laikyti, kad skaičius C priklauso Mandelbroto aibei.

Prieš pradedant gaminti savadarbius fraktalus, mums reikia sužinoti aibės ribas ir tuo pačiu nusistatyti keletą konstantų. Koordinačių plokštumoje, fraktalo reliatyvus dydis yra nekintantis. X ašyje z “nepabėga” į begalybę intervale [-2, 0.25]. Y ašyje – [-1, 1]. Kad fraktalas neatrodytų suspaustas, programoje naudosime intervalą [-2.2, 1] X ašiai ir [-1.2, 1.2] Y ašiai. Tokios ribos mums duos proporciją lygią 1.333333, kas reiškia, kad nurodę plotį 1024, aukštį gausime 768. Patogu, ar ne? ;)

Atkreipkite dėmesį, kad X ašyje yra realieji skaičiai, o Y – menamieji.

Žiūrime ką turime:

    #kiek iteracijų darysime prieš nuspresdami,
    #kad skaičius priklauso aibei.
    max_i = 200

    #reliatyvūs dydžiai
    r_width = 3.2
    r_height = 2.4

    #pagal proporciją parinksime aukštį
    ratio = r_width / r_height

    #plotis bus nurodomas vartotojo
    width = int(sys.argv[1])
    height = int(width / ratio)

    #žingsnelis padės apskaičiuoti konkretaus pixelio koordinatę
    x_step = r_width / width
    y_step = r_height / height

    #realiųjų skaičių ašis
    x_axis = arange(-2.2, 1.0, x_step).tolist()
    #menamųjų skaičių ašis
    y_axis = arange(-1.2, 1.2, y_step).tolist()

Ašims generuoti naudojame arange() (ne arrange!) funkciją, nes range() nepriima float tipo argumentų. Funkciją galima importuoti iš Numeric modulio.

Dabar apsirašysime reikalingas funkcijas. Pradėkime nuo skaičiaus z modulio. Jo radimui pritaikysime pitagoro teoremą. Funkcijos rezultatas yra modulio kvadratas, tad tikrindami ar skaičius priklauso aibei, lyginsime ne su dvejetu, o 22 t.y. 4.

def r_sq(z):
    """
        Grąžina skaičiaus z modulį.
    """
    return z.real**2 + z.imag**2

Dabar pagrindinė funkcija: mandelbrot(). Vienintelis būtinas argumentas – C. Funkcija yra rekursinė, z pradedamas nuo 0+0j ir kaskart jam pritaikoma formulė z = z2 + C. Funkcijos rezultatas – iteratorius i. Pagal jo reikšmę nustatysime kokią spalvą naudoti pikseliui.

def mandelbrot(c, z=0+0j, i=0):
    """
        Checks if the number escapes to infinity
    """
    if r_sq(z) > 4:
        return i
    elif i == max_i:
        return -1
    else:
        return mandelbrot(c, (z**2 + c), i+1)

Keisdami laipsnį, kuriuo keliamas z, galite išgauti ir kitokias figūras:

z^3

z^3

z^5.5

z^5.5

z^16

z^16

Jei darytumėm dvispalvį paveikslą, to mums pakaktų, tačiau spalvotam fraktalui reikia ir spalvinimo funkcijos. Nekursime nieko sudėtingo, panaudosime quick&dirty metodą – spalvų indeksą.

def get_color(iterations):
    """
        Grąžina spalvos kodą, priklausomai nuo to, kada
        (jei išvis) skaičius "pabėgo" į begalybę
    """

    #atrodo, kad schema yra BGR, ne RGB :))
    index = {
        #iteracijos    #spalva
        (0, 1):        0xff0000,
        (1, 2):        0xff1000,
        (2, 3):        0xff2000,
        (3, 4):        0xff3000,
        (4, 5):        0xff4000,
        (5, 6):        0xff5000,
        (6, 7):        0xff6000,
        (7, 8):        0xff7000,
        (8, 10):       0xff9000,
        (10, 15):      0xffa600,
        (15, 21):      0xffb500,
        (21, 50):      0xffdf00,
        (50, max_i+1): 0xffffff,
    }
    for point in index:
        if iterations in range(point[0], point[1]):
            return index[point]

Visas pixelių spalvas laikysime sąraše, kuris poto labai lengvai bus “sumetamas” į paveiksliuką.

    #sukuriame kintamąjį duomenims
    out = []

    for y in y_axis:
        for x in x_axis:
            result = mandelbrot(complex(x, y))
            if result < 0:
                #taškai, priklausantys aibei (juodi)
                out.append(0x000000)
            else:
                #taškai, kurie nepriklauso (gaunam spalvą)
                out.append(get_color(result))

Paveikslėlio generavimui naudojam PIL biblioteką. Naudojimas labai paprastas:

    img = Image.new('RGB', (width, height))
    img.putdata(out)
    img.save('m.png')

Turintys python ir norintys išbandyti viską “dykai” gali atsisiųsti scenarijų. Nesitikėkite nieko ypatingo. Programa neturi nei anti-aliasing savybės, nei tolygaus spalvų perėjimo. Įrašas skirtas daugiau supažindinimui, ir džiaugsmo “va pasidariau pats” patyrimui. Norintys kažko “geriau” gali naudoti specializuotas programas :)

Jei patobulinsite šį kodą, ar šiaip turite pastabų – parašykite komentaruose kas ir kaip.