Háttér
A fraktáloknak egyelőre nincs konszenzussal elfogadott teljeskörű matematikai definíciója – a felmerült definíciók esetén mindig találtak olyat, melyet tulajdonságai alapján fraktálnak lehet tekinteni, ám e szempontból érdektelen (pl. az önhasonlóság az egyenes esetén is teljesül), vagy olyat, amit érdemes lehet tárgyalni, pedig eléggé különböző (pl. az itt tárgyalt absztrakt, determinisztikus fraktálok konstrukciója nyilván sokban eltér a természeti formákat utánzó sztochasztikus fraktáloktól, mégis érződik elvi hasonlóság).
A szabatos definíció hiánya ugyanakkor nyilván nem jelenti azt, hogy a fraktálok vizsgálata ne vetne fel számos érdekes problémát.
Az illusztráción szereplő alakzat a Koch hópehely, az egyik legelső és talán legismertebb olyan fraktál, mely egyértelmű helyettesítési szabályok alapján, rekurzív módon generálható.
A kiinduló forma (initiator) egy szabályos sokszög, a helyettesítési szabály (generator) pedig az, hogy a sokszög oldalait elharmadoljuk, majd minden oldal középső harmadát egy szabályos háromszög két másik oldalával helyettesítjük.
E helyettesítést rekurzív módon ismételve az oldalhosszak összege (a síkidom kerülete) minden lépésben 1⅓ mértékben növekszik – azaz elvileg (a végtelenedik iterációban) végtelen.
Mindeközben területe nyilvánvalóan véges, hiszen sehol nem lép ki a kiinduló síkidom köré írt kör területéről.
Egy lehetséges megoldás: LETÖLTÉS
A fraktálok leírásának egy bevett formális nyelve az L-rendszer (Lindenmayer Arisztid után), melynek nyelvtani szabályait iteratív módon kell alkalmazni.
E rekurzív jellegből adódó önhasonlóság révén a fraktálszerű formák könnyen leírhatók.
Ha minden szimbólumhoz pontosan egy kimenet kapcsolódik, akkor a rendszer determinisztikus, ha több (és mindegyiket bizonyos valószínűséggel választjuk ki minden iteráció során), akkor sztochasztikus.
A Koch-hópehely egyszerűen leírható L-rendszerként
egyetlen változó (f), és két konstans (+, –) segítségével.
Az f („draw forward”) egy szakaszt rajzol, a + balra fordulást (120°), a – jobbra fordulást (60°) jelent.
A kiinduló szabályos háromszög leírható f+f+f formában,
a helyettesítési szabály pedig f → f-f+f-f formában
(ez a SUBSTITUTE() függvénnyel könnyen megvalósítható).
Az első iteráció így a következőképp néz ki: f–f+f–f+f–f+f–f+f–f+f–f.
A következő iteráció sajnos emberi fogyasztásra már kevéssé alkalmas: f–f+f–f–f–f+f–f+f–f+f–f–f–f+f–f+f–f+f–f–f–f+f–f+f–f+f–f–f–f+f–f+f–f+f–f–f–f+f–f+f–f+f–f–f–f+f–f.
A rendszer számunkra kényelmetlen tulajdonsága az is, hogy relatív szögeket alkalmaz, azaz a következő szegmens irányát az előző szegmens irányához képest adja meg – ezért egy másik rendszer alkalmazását javasoljuk.
Paraméterek
• Rendszámok
A helyettesítési szabály egy lehetséges reprezentációja, hogy a teljes fraktált egy többjegyű számként kezeljük.
- A nulladik lépésben a szám egyjegyű, lehetséges értékei nullától a kiinduló poligon oldalszámáig terjed (azt el nem érve, tehát △pgn=3 esetén 0-1-2).
- A szám minden lépésben újabb számjeggyel bővül, mely annyiféle értéket vehet fel, ahány új szegmenssel helyettesítjük az iteráció során az eredeti szakaszt (itn=4 esetén 0-1-2-3).
- Eszerint adott it iterációs lépésben a fraktál egyes szegmenseinek irányait
egy itn alapú számrendszerbéli számsorozat
első pgn · itnit darab eleme írja le.
A Koch-hópehely esetében az első iteráció (3 oldal 4 szegmensre osztva) leírható a négyes számrendszer első 12 számaként:
00, 01, 02, 03, 10, 11, 12, 13, 20, 21, 22, 23
Hozzuk létre a táblázatban a szükséges darabszám-paramétereket!
- Nevezzünk el egy cellát it-nek (B1), és adjuk meg ott az ábrázolni kívánt iterácók számát (pl. 4)!
- Nevezzünk el egy cellát pgn-nek (B4), és adjuk meg ott a kiinduló poligon oldalszámát (3)!
- Nevezzünk el egy cellát itn-nek (B17), és adjuk meg ott az egyes iterácós lépéseknél adódó szegmensek számát (4)!
- Nevezzünk el egy cellát n-nek (B19), és számoljuk ki a szegmensek számát (pgn·itnit)!
=pgn*itn^it
• Méretek
A fraktál befoglaló mérete az iteráció során nem változik, hiszen épp azt akarjuk illusztrálni, hogy a kerület csak a pontosabb mérés, azaz a „rövidebb vonalzó” miatt növekszik.
- A nulladik lépésben a kiinduló poligon oldalhossza (pgl) természetesen szabadon felvehető.
- Az iteráció során minden lépésben az adott generálási szabálynak megfelelő arány szerint (itl) változik a szegmensek hossza.
Hozzuk létre a táblázatban a szükséges méret-paramétereket!
- Nevezzünk el egy cellát pgl-nek (B3), és adjuk meg ott a kiinduló poligon oldalhosszát (3,0)!
- Nevezzünk el egy cellát itl-nek (B16), és adjuk meg ott az egyes iterácós lépéseknél adódó szegmensek hosszának arányát (1/3)!
- Nevezzünk el egy cellát l-nek (B18), és számoljuk ki a szegmensek hosszát (pgl·itlit)!
=pgl*itl^it
- Nevezzünk el egy-egy cellát xs-nek (B5), és adjuk meg ott a kezdőpont x koordinátáját (pl. –½pgl)!
- Nevezzünk el egy-egy cellát ys-nek (B6), és adjuk meg ott a kezdőpont y koordinátáját (pl. –⅓ · 3½ · pgl/2)!
• Irányok
Természetesen a fenti rendszámok csak a logikai vázat írják le, önmagukban nem árulják el az egyes szegmensek irányát.
- A legelső számjegy a fentiek értelmében a a kiinduló sokszög oldalait reprezentálja, annak „átváltása” (szabályos poligon esetén) automatikus: mivel a poligon külső szögeinek összege 360°, ha az első él vízszintes (0°), az i. él szöge i · 360°/pgn lesz.
- A többi számjegy az iterációs lépés révén adódó szegmenseket reprezentálja – ezek szögei definiálják a fraktálra jellemző egyedi mintázatot.
Hozzuk létre a táblázatban az irányokat megadó paraméter-tömböket!
- Számoljuk ki a kiinduló poligon éleinek (i · 360°/pgn) irányait (B7:B11)!
=SEQUENCE(pgn; ; 0; (360/pgn))
=SORSZÁMLISTA(pgn; ; 0; (360/pgn))
- Nevezzük el ezt a tartományt dinamikus módon _pgd-nek!
=Sheet1!$B$7#
- Egymás alatti cellákba írjuk be az iteráció során létrejövő szegmensek szögeinek sorozatát (B21:B24)!
0 | -60 | 60 | 0
- Nevezzük el ezt a tartományt dinamikus módon _itd-nek!
=OFFSET(Sheet1!$B$21; ; ; itn)
=ELTOLÁS(Sheet1!$B$21; ; ; itn)
Szögek
A fraktál ábrázolásához értelemszerűen minden egyes szakaszának koordinátáját ki kell számoljuk.
Ennek egy egyszerű megoldása, ha lekövetjük a kezdőpontból indított vonal útját – mivel minden szegmens hossza azonos, ehhez tulajdonképp elegendő azt ismernünk, hogy az aktuális pontból induló él milyen irányú.
Erre alkalmas az előbbiekben vázolt rekurzív megoldás.
A program sajnos nem tud tetszőleges számrendszerben számolni, így nem számíthatunk arra a vizuális segítségre, mely a számra nézve azonnal mutatja, hogy hányadik iterációs szintnél járunk. Az egyes „számjegyek” értékei viszont enélkül is elég egyszerűen kinyerhetők.
Hozzuk létre az élek irányait megadó tömb sorait és oszlopait!
- Egy üres oszlop második cellájától hozzuk létre a szükséges számú élt reprezentáló n darab sort!
=SEQUENCE(n; ; 0)
=SORSZÁMLISTA(n; ; 0)
- Nevezzük el ezt a tartományt dinamikus módon _i-nek!
- Az előbbi oszloptól jobbra, az első sorban hozzuk létre az egyes iterációs generációkat jelző it+1 elemű, csökkenő számsort!
=SEQUENCE(1; it+1; it; -1)
=SORSZÁMLISTA(1; it+1; it; -1)
- Nevezzük el ezt a tartományt dinamikus módon _g-nek!
- A két dinamikus tartomány alapján a rendszám számjegyei a maradékos osztás révén generálhatók.
=MOD(INT(_i / itn^_g); itn)
=MARADÉK(INT(_i / itn^_g); itn)
A fenti képlet elsőre talán nem triviális – érdemes végiggondolni, melyik sorban és oszlopban mit is csinál.
Érdemes talán megjegyezni, hogy könnyen lehet olyan szabályt kitalálni, amelynél az iterácós lépéseknél adódó szegmensek száma (itn) éppen tíz – ekkor nyilván a rendszám is a tízes számrendszerben lesz (a képen pgn= 4).
- itl= 1/6
- itn= 10
- _itd= 0 | 60 | -60 | 60 | 60 | -60 | -60 | 60 | -60 | 0
Mivel itt a megszokott tízes számrendszert kell használni, talán könnyebb megérteni, mi történik.
- A jobb oldali oszlopban a _g (generáció) értéke mindig 0, így itng értéke 1, az eggyel való osztás pedig nyilván nem módosít az értéken, azaz a képlet az aktuális sor _i (sorszám) értékének _itn-nel való osztásának maradékát adja meg – a tízzel való osztás maradéka pedig nyilván a szám utolsó számjegye.
- Innen balra haladva _g értéke minden oszlopban eggyel nő, miáltal itng értéke hatványozódik (1, 10, 100) – így tudjuk „levágni” az épp vizsgálni kívánt számjegy alatti helyiértékeket.
Valójában azonban minket nem maga a rendszám érdekel, hanem annak aktuális számjegyei alapján kalkulálható szög. Mint láttuk, ezt nem is egy helyről kell kiolvasni, hanem az első oszlop esetén a poligon oldalainak _pgd tömbjéből, a többi esetben az iteráció _itd tömbjéből – a valós érték pedig ezek összege lesz.
Hozzuk létre az élek irányait megadó tömböt!
- Alakítsuk át a képletet úgy, hogy a viszonylag bonyolult számolás megismétlése helyett elég legyen egy rövidítésre hivatkozni!
=LET( S; INT(_i / itn^_g); MOD(S; itn))
=LET( S; INT(_i / itn^_g); MARADÉK(S; itn))
- Alakítsuk át ismét a képletet, hogy az első oszlop esetén a poligon oldalainak _pgd tömbjéből, a többi esetben az iteráció _itd tömbjéből olvassa ki a számjegynek megfelelő értéket!
=LET( S; INT(_i/itn^_g); IF(_g=it; INDEX(_pgd;S+1); INDEX(_itd; MOD(S;itn)+1)))
=LET( S; INT(_i/itn^_g); HA(_g=it; INDEX(_pgd;S+1); INDEX(_itd; MARADÉK(S;itn)+1)))
- Nevezzük el ezt a tartományt dinamikus módon _d-nek!
Koordináták
Az előbbi _d tömb tehát megadja az egyes iterációs lépésekből örökölt elforgatási szögeket, csak össze kell adni azokat.
Számoljuk ki az egyes élek valós szögét!
- Egy üres oszlop második sorába (W2) keressük ki az első élhez tartozó összes szöget!
(Ha lefelé másoljuk a képletet, látható lesz a rendszer.)
=XLOOKUP(@_i; _i; _d)
=XKERES(@_i; _i; _d)
- Nekünk mindezen szögek összegére van szükségünk, módosítsuk tehát a képletet eszerint!
(A 360°-kal való osztás révén kiszűrjük a negatív szögeket)
=MOD(SUM(XLOOKUP( @_i; _i; _d));360)
=MARADÉK(SZUM(XKERES( @_i; _i; _d));360)
- Mivel ez a képlet nem alkot dinamikus tartományt, másoljuk le a képletet az első pár ezer sorba!
- Ahol a tartomány túllóg az eredeti _i sorszám sorain, ott hibaüzenetet kapunk, amit érdemes kivédeni!
=IFERROR(MOD(SUM(XLOOKUP( @_i; _i; _d));360);"·")
=HAHIBA(MARADÉK(SZUM(XKERES( @_i; _i; _d));360);"·")
- Nevezzük el a tartományt dinamikus módon _a-nak!
=OFFSET(Sheet1!$W$2;;; n)
=ELTOLÁS(Sheet1!$W$2;;; n)
Az előbbi módon kapott _a tömb tehát megadja minden él (abszolút) irányát.
Mivel az élek hossza is ismert (l), akár már meg is lehet rajzolni a fraktált.
Ahhoz viszont, hogy diagramot készíthessünk, szükség van minden pont koordinátájára.
Számoljuk ki az egyes élek végpontjainak koordinátáit!
- Két egymás melletti üres oszlop első sorába (pl.X1, Y1) írjuk be a startpont x és y koordinátáit!
=xs | =ys
- Az oszlopok második cellájától kezdve ehhez adjuk hozzá az aktuális él x és y vetületi hosszát!
Mivel ezek a képletek nem alkotnak dinamikus tartományt, manuálisan kell lemásoljuk őket – és mivel ahol e tartomány túllóg az eredeti _i sorszám sorain, ott hibaüzenetet kapunk, érdemes azt kivédeni!
=X1 +IFERROR(l* COS(RADIANS(@_a)); 0)
=X1 +HAHIBA(l* COS(RADIÁN(@_a)); 0) |
=Y1 +IFERROR(l* SIN(RADIANS(@_a)); 0)
=Y1 +HAHIBA(l* SIN(RADIÁN(@_a)); 0)
Az x és y koordináták ismeretében már nincs más teendő, mint a diagram beszúrása.
Hozzuk létre a koordináták által leírt sokszög diagramját!
- Jelöljük ki a koordináták tartományát (pl. X1:Y40000)!
- Válasszuk a Beszúrás Insert menüben a Diagramok Charts csoportból a Pont vonalakkal Scatter with Straight Lines típust!
- Mivel az ábra tengelyei nem automatikusan arányhelyesek, érdemes az x és y tengelyek minimum és maximum értékét azonosra állítani, és a diagram arányát négyzetesre igazítani.
Mivel a vázolt megoldás alkalmas a fraktálok egy teljes családjának ábrázolására, érdemes kicsit játszani az egyes paraméterekkel, hogy megtapasztaljuk, milyen lehetőségeket rejtenek.