Jedno arytmetyczne działanie powtarzane
Zbiór Mandelbrota jest zdefiniowany przez iterację tak krótką, że mieści się w tweecie. Weź liczbę zespoloną c. Zacznij od z = 0 i stosuj, raz po razie:
z_{n+1} = z_n² + c gdzie z_0 = 0 c należy do zbioru ⟺ sekwencja pozostaje ograniczona na zawsze c nie należy do zbioru ⟺ sekwencja ucieka w nieskończoność To jest cała definicja. Każda spirala, każdy filament, każda miniaturka kopii zbioru ukryta pod trilionem zoomów głęboko wewnątrz jest konsekwencją kwadratowania i dodawania. Zbiór nazwano na cześć Benoïta Mandelbrota, który wygenerował pierwsze obrazy tego zbioru w IBM w 1980 roku i dostrzegł, co tam widział; podległe dynamiki zespolone były badane przez Pierre Fatou i Gastona Jullię około 1918 roku, całkowicie bez komputerów.
z_{n+1} = z_n² + c with z_0 = 0
c is IN the set ⟺ the sequence stays bounded forever
c is OUT of the set ⟺ the sequence runs away to infinity
Algorytm czasu ucieczki
Nie można iterować w nieskończoność, dlatego potrzebujesz testu, który kończy się. Zapewnia go mały twierdzenie: jeśli |z| kiedykolwiek przekroczy 2, sekwencja jest gwarantowany rozbieżności i nigdy nie powróci. Powód jest prosty – gdy |z| > 2 i |z| ≥ |c|, kwadratowanie rośnie szybciej niż dodawanie c może je ograniczyć. Zatem promień ucieczki wynosi 2, a nie jest to przybliżenie.
funkcja escape(cx, cy, maxIter) { let x = 0, y = 0, x2 = 0, y2 = 0, i = 0; while (x2 + y2 <= 4 && i < maxIter) { // porównanie kwadratów: bez pierwiastka y = 2 * x * y + cy; // 3 mnożenia na x = x2 - y2 + cx; // iterację, a nie 4 x2 = x * x; y2 = y * y; i++; } return i; // i === maxIter → załóż, że wewnątrz }
function escape(cx, cy, maxIter) {
let x = 0, y = 0, x2 = 0, y2 = 0, i = 0;
while (x2 + y2 <= 4 && i < maxIter) { // compare squares: no sqrt
y = 2 * x * y + cy; // 3 multiplications per
x = x2 - y2 + cx; // iteration, not 4
x2 = x * x;
y2 = y * y;
i++;
}
return i; // i === maxIter → assume inside
}
Jak to sprawić, żeby było gładko i szybko
Kolorowanie na podstawie surowego licznika całkowitego i jest generowane twarde, koncentryczne paski, ponieważ i jest funkcją krokową. Rozwiązaniem jest znormalizowana liczba iteracji, która interpoluje między paskami w oparciu o to, jak daleko poza promień ucieczki przeszedł ostatecznie z >z<. // po pętli uciekł z |z|² = x2 + y2 const logZn = Math.log(x2 + y2) / 2; const nu = Math.log(logZn / Math.log(2)) / Math.log(2); const smooth = i + 1 - nu; // liczba rzeczywista, a nie całkowita Podaj tę rzeczywistą liczbę jako podliczniki do palety i paski rozpuszczają się w ciągłą gradient. Dwa inne standardowe przyspieszenia mają znacznie większe znaczenie niż optymalizacja na poziomie mikroskopowym wewnętrznej pętli:
wewnętrzne kontrole Główny kardioida i okresowy bulbi drugiego stopnia mają dokładne algebraiczne testy. Punkty wewnątrz nich znajdują się w zbiorze, więc pomiń iterację całkowicie. To samo usuwa większość pracy ograniczonej maxIter w pełnym zestawie. podawanie cykli Ograniczony orbitą ostatecznie cykluje. Utrzymuj odniesienie detection) orbit powraca do zewnątrz epsilon od niego — punkt jest w zestawie i nie ma potrzeby osiągnąć maxIter. Test kardioidy wart jest zapisu, ponieważ jest tani i oszczędza dużo pracy.
// after the loop escaped with |z|² = x2 + y2 const logZn = Math.log(x2 + y2) / 2; const nu = Math.log(logZn / Math.log(2)) / Math.log(2); const smooth = i + 1 - nu; // a real number, not an integer
Co właściwie stanowi granica
Wnętrze jest matowe; cała struktura znajduje się na granicy. Trzy fakty, które warto zapamiętać. Po pierwsze, zbiór jest połączony – Adrien Douady i John Hubbard udowodnili to w 1982 roku. Wyspy, które widzi się pływające w czarnym morzu podczas dużego powiększenia, nie są wyspami: są to połączenia główną masą przez włókna zbyt cienkie, aby je wyświetlić.
Po drugie, granica ma wymiar Hausdorffa dokładnie równy 2 – Mitsuhiro Shishikura, 1998 – pomimo braku powierzchni. Jest tak zgięta jak jakaś krzywa w płaszczyźnie może być. Po trzecie, zbiór jest samo-podobny tylko przybliżnie: małe kopie rozsiane po włóknach nie są dokładnymi skalowaniami całego zbioru i każda z nich jest inaczej udekorowana. Zbiór Mandelbrota jest fraktalem w sensie nieskończonego szczegółu na każdym poziomie, a nie w sensie dokładnej samo-podobieństwa jak trójkąt Sierpińskiego.
Istnieje również bezpośrednia relacja z zbiorami Julia, które używają tej samej iteracji, ale utrzymują c stałe i zmieniają z₀. Zbiór Mandelbrota jest dokładnie mapą, która c daje połączony zbiór Julia: wybierz c wewnątrz niego i odpowiadający mu zbiór Julia będzie jednym ciągłym kawałkiem; wybierz go poza nim i zbiór Julia rozpada się na kurz Cantora. Każdy punkt zbioru Mandelbrota jest miniaturowym indeksem do innego zbioru Julia – dlatego eksplorator pokazujący oba strony obok siebie jest znacznie bardziej pouczający niż którykolwiek z nich osobno.
Ściana liczb zmiennoprzecinkowych
Przyciągnij się wystarczająco daleko i obraz zamienia się w bezkształtny bąk — nie dlatego, że zestaw wyczerpuje szczegółowość, ale ponieważ podwójna precyzja wyczerpuje cyfry. 64-bitowe podwojne liczby przenoszą około 15–16 znaczących cyfr dziesiętnych, więc gdy szerokość widoku spadnie do w przybliżeniu 10⁻¹⁵ oryginalnych sąsiednich pikseli, zaokrąglają się one do tej samej wartości i obraz staje się blokowy.
Poważni głębokie renderery zoomu unikają tego dzięki teorii perturbacji: oblicz jeden orbitę o wysokiej precyzji w centrum widoku przy użyciu arytmetyki zmiennoprzecinkowej, a następnie wyraź każdy inny piksel jako mały delta względem tej orbity i iteruj deltę przy użyciu zwykłej podwójnej precyzji. Delty pozostają małe, więc strata dokładności nie jest znacząca, a potrzebna jest tylko jedna orbita z wolnym obliczeniem dużych liczb. Dodaj szereg aproksymacji, aby pominąć pierwsze tysiące iteracji każdej orbity delta, a zoomy daleko poza 10⁻³⁰⁰ stają się praktyczne — co czyni głębokie filmy zoomowe, które widziałeś, możliwymi. Jedną wadą są artefakty: piksele, których orbita przechodzi zbyt blisko zera względem odniesienia, tracą dokładność i muszą być wykrywane i ponownie renderowane przeciwko drugiemu odniesieniu.
Często zadawane pytania
Dlaczego promień ucieczki jest dokładnie równy 2?
Wynika to z faktu, że gdy moduł |z| przekracza 2 (i |z| ≥ |c|), kwadratowanie powoduje szybsze wzrost modułu niż dodawanie c może go przyciągnąć z powrotem, co gwarantuje, że sekwencja ucieknie w nieskończoność. Jest to udowodniony próg, a nie empiryczna wartość – żaden orbit, który opuści dysk o promieniu 2, nigdy nie powróci.
Dlaczego mój zoom głęboki zamienia się w bloki?
Dwupozycyjne zmiennoprzecinkowe liczby zmiennopunktowe przenoszą tylko około 15-16 cyfr znaczących. Gdy obszar widzenia wynosi około 10⁻¹⁵ oryginalnej szerokości, sąsiednie piksele zaokrąglają się do tego samego współrzędnego. Renderery deep-zoom unikają tego dzięki teorii perturbacji: jeden orbit o arbitralnej precyzji plus małe delta na piksel iterowane w podwójnej precyzji.
Czy zbiór Mandelbrota jest samopodobny?
Nie do końca. Posiada nieskończoną szczegółowość na każdym poziomie i zawiera niezliczone małe kopie samego siebie, ale te kopie są zniekształcone i każda jest inna – dlatego nie jest tożsamość samopodobieństwa w taki sposób jak trójkąt Sierpińskiego lub krzywa Koch. Jego granica ma wymiar Hausdorffa 2, podczas gdy otacza zero powierzchni.
Wypróbuj na żywo
Wszystko powyżej działa bezpośrednio w Twojej przeglądarce — otwórz Mandelbrot Set Explorer i zmieniaj parametry podczas działania. Nic nie jest instalowane ani przesyłane na serwer, cały model działa w jednej karcie.
▶ Otwórz symulację Mandelbrot Set Explorer