Jak ka偶dego zdrowego faceta po czterdziestce, od czasu do czasu 艂apie mnie straszna ch臋tka na napisanie jakiego艣 interesuj膮cego algorytmu na losowy spacer na dwuwymiarowej p艂aszczy藕nie. Starzy (w sensie sta偶u, niekoniecznie biologicznym) Czytelnicy blogu by膰 mo偶e kojarz膮 te wpisy: 1, 2, 3, 4 - jak wida膰 wytyczanie 艣cie偶ek na p艂aszczy藕nie wraca do mnie jak bumerang.
Ostatnio zn贸w mnie nasz艂o - tym razem z g艂upia frant spr贸bowa艂em czego艣 nast臋puj膮cego:
- Startujemy w puncie (0,0), z azymutem 2掳.
- Robimy krok.
- Znajdujemy kolejn膮 liczb臋 pierwsz膮 po dw贸jce (czyli 3)
- Zakr臋camy wi臋c o 3掳 - teraz nasz azymut to 5掳
- Robimy kolejny krok.
- Po tr贸jce kolejna liczba pierwsza to 5
- Zakr臋camy wi臋c o 5掳 czyli nasz obecny azymut to 10掳
- Robimy krok
I tak dalej, przez, dajmy na to, 100 albo 1000 albo 100000 krok贸w. Jak b臋dzie wygl膮da膰 nasza 艣cie偶ka?
Okazuje si臋, 偶e ca艂kiem inaczej od wszystkich losowych 艣cie偶ek, kt贸re do tej pory uda艂o mi si臋 wypoci膰!
Je偶eli ograniczymy si臋 do 50 pierwszych krok贸w, wynik jest niezbyt imponuj膮cy:

Ale ju偶 zwi臋kszenie licznika do stu krok贸w daje intryguj膮cy efekt:

Co si臋 tutaj wydarzy艂o?
W 艣rodku spiralki jest taka g臋stwina, bo nasze zakr臋ty robi膮 si臋 bardzo ostre (o wi臋cej ni偶 k膮t prosty) wi臋c w zasadzie drepczemy w miejscu (technicznie nie w miejscu tylko w niewielkim obszarze). Ale poniewa偶 ko艂o jest ko艂em i zakr臋t w pewnym momencie przekracza 270 stopni - czyli k膮t prosty wywr贸cony na lew膮 stron臋 - zabazgrawszy spiralk臋 "uciekamy" od niej prostuj膮cym si臋 kawa艂kiem krzywej. Po dotarciu do 360 stopni (to ten kawa艂ek w miar臋 prostej trasy mi臋dzy dwiema spiralkami) sytuacja powtarza si臋 i produkujemy kolejn膮 spiralk臋. A co je偶eli zwi臋kszymy teraz liczb臋 krok贸w do 1000?

To ju偶 wygl膮da ca艂kiem ciekawie. Do艣膰 losowo a jednocze艣nie jakby powtarzalnie. Fraktal to to nie jest, ale i tak mi si臋 podoba.
Dalsze zwi臋kszanie liczby krok贸w nie daje ju偶 zbyt osza艂amiaj膮cych efekt贸w. Na przyk艂ad dla 100000 dostajemy takie co艣:

Owszem, fajne, ale mo偶na by to jako艣 podrasowa膰.
Co by tu...
Zacznijmy od tego, 偶e dobrze by艂oby troch臋 z艂agodzi膰 te ostre kanty na zakr臋tach.
Tutaj oryginalny kod:
import matplotlib.pyplot as plt from math import radians, sin, cos from sympy import nextprime xlist, ylist = [0.0], [0.0] x, y = 0.0, 0.0 angle_degrees = 2 angle_radians = radians(angle_degrees) lw = '0.5' # line width for n in range(500): angle_degrees = nextprime(angle_degrees) angle_radians += radians(angle_degrees) x += cos(angle_radians) y += sin(angle_radians) xlist.append(x) ylist.append(y) plt.xticks([]) plt.yticks([]) plt.box(False) plt.plot(xlist, ylist, linewidth=lw) plt.show()
... a tutaj drobna zmiana, dzi臋ki kt贸rej zakr臋ty staj膮 si臋 bardziej 艂agodne (kosztem jednakowo偶 konieczno艣ci zwi臋kszenia liczby krok贸w):
import matplotlib.pyplot as plt from math import radians, sin, cos from sympy import nextprime xlist, ylist = [0.0], [0.0] x, y = 0.0, 0.0 angle_degrees = 2 angle_radians = radians(angle_degrees) lw = '0.5' # line width for n in range(10000): angle_degrees = nextprime(angle_degrees) angle_radians += radians(angle_degrees/20) x += cos(angle_radians) y += sin(angle_radians) xlist.append(x) ylist.append(y) plt.xticks([]) plt.yticks([]) plt.box(False) plt.plot(xlist, ylist, linewidth=lw) plt.show()
Wynik:

No dobra. Co by tu jeszcze...
Jest jakby troch臋 monochromatycznie. Spr贸bujmy doda膰 kolork贸w. Tylko jak?
Zmodyfikujemy nasz kod tak, 偶eby linia zmienia艂a kolor za ka偶dym razem kiedy natrafimy na liczb臋 pierwsz膮 bli藕niacz膮:
import matplotlib.pyplot as plt from math import radians, sin, cos from sympy import nextprime from itertools import cycle xlist, ylist = [0.0], [0.0] x, y = 0.0, 0.0 angle_degrees = 2 angle_radians = radians(angle_degrees) lw = '0.5' # line width colors = cycle('bgrcmk') for n in range(10000): previous_angle = angle_degrees angle_degrees = nextprime(angle_degrees) if (angle_degrees-previous_angle == 2): plt.plot(xlist, ylist, linewidth=lw, c = next(colors)) xlist, ylist=[x],[y] angle_radians += radians(angle_degrees/20) x += cos(angle_radians) y += sin(angle_radians) xlist.append(x) ylist.append(y) plt.xticks([]) plt.yticks([]) plt.box(False) plt.show()
Wynik:

Nie podoba mi si臋. Kolorki w 艣rodku spiral nak艂adaj膮 si臋 na siebie, nadal jest nudno. Co by tu zamiast tego...
A gdyby tak zmienia膰 kierunek, w kt贸rym zakr臋camy, na przeciwny za ka偶dym razem kiedy trafimy na liczb臋 pierwsz膮 bli藕niacz膮?
Hmmm.
import matplotlib.pyplot as plt from math import radians, sin, cos from sympy import nextprime xlist, ylist = [0.0], [0.0] x, y = 0.0, 0.0 angle_degrees = 2 angle_radians = radians(angle_degrees) lw = '0.5' # line width flip = 1 for n in range(10000): previous_angle = angle_degrees angle_degrees = nextprime(angle_degrees) if (angle_degrees-previous_angle == 2): flip *= -1 angle_radians += flip * radians(angle_degrees/20) x += cos(angle_radians) y += sin(angle_radians) xlist.append(x) ylist.append(y) plt.xticks([]) plt.yticks([]) plt.box(False) plt.plot(xlist, ylist, linewidth=lw) plt.show()

I to ju偶 jest jako艣ciowa zmiana - wida膰 wyra藕nie miejsca, w kt贸rych 艣cie偶ka zakr臋ca raz w jedn膮, raz w drug膮 stron臋 bez zapadania si臋 w "studni臋 grawitacyjn膮" kolejnej spiralki.
Co by tu jeszcze...
Przywr贸膰my pomys艂 z kolorowaniem (z poprzedniej wersji skryptu) zachowuj膮c jednocze艣nie pomys艂 z zakr臋caniem:
import matplotlib.pyplot as plt from math import radians, sin, cos from sympy import nextprime from itertools import cycle xlist, ylist = [0.0], [0.0] x, y = 0.0, 0.0 angle_degrees = 2 angle_radians = radians(angle_degrees) lw = '0.5' # line width flip = 1 colors = cycle('bgrcmk') for n in range(10000): previous_angle = angle_degrees angle_degrees = nextprime(angle_degrees) if (angle_degrees-previous_angle == 2): flip *= -1 plt.plot(xlist, ylist, linewidth=lw, c = next(colors)) xlist, ylist=[x],[y] angle_radians += flip * radians(angle_degrees/20) x += cos(angle_radians) y += sin(angle_radians) xlist.append(x) ylist.append(y) plt.xticks([]) plt.yticks([]) plt.box(False) plt.show()

呕eby za ka偶dym razem skrypt generowa艂 nam nieco inn膮 艣cie偶k臋, mo偶na doda膰 odrobin臋 losowo艣ci:



import matplotlib.pyplot as plt from math import radians, sin, cos from sympy import nextprime from itertools import cycle from random import random xlist, ylist = [0.0], [0.0] x, y = 0.0, 0.0 angle_degrees = 2 angle_radians = radians(angle_degrees) lw = '0.5' # line width flip = 1 colors = cycle('bgrcmk') for n in range(10000): previous_angle = angle_degrees angle_degrees = nextprime(angle_degrees) if (angle_degrees-previous_angle == 2): flip *= -1 plt.plot(xlist, ylist, linewidth=lw, c = next(colors)) xlist, ylist=[x],[y] angle_radians += flip * radians(angle_degrees/20) * (1 + random()/1000.0) x += cos(angle_radians) y += sin(angle_radians) xlist.append(x) ylist.append(y) plt.xticks([]) plt.yticks([]) plt.box(False) plt.show()
Temat mo偶na ci膮gn膮膰 w zasadzie bez ko艅ca, ale chwilowo mam do艣膰. Jestem jednak przekonany, 偶e losowe spacery jeszcze si臋 tutaj pojawi膮 nie raz.
To jest spaghetti code.
Zgadza si臋 – a zatem smacznego! 馃檪