Mandelbrot set: Difference between revisions
Content added Content deleted
m (→Normal Map Effect, Mercator Projection and Perturbation Theory: Made changes to match Arnaud Chéritat's image.) |
(→Normal Map Effect, Mercator Projection and Deep Zoom Images: The original coloring methods added and subheadings inserted; Comments shortened) |
||
Line 10,902: | Line 10,902: | ||
===Normal Map Effect, Mercator Projection and Deep Zoom Images=== |
===Normal Map Effect, Mercator Projection and Deep Zoom Images=== |
||
====Smoothing, Normalization and Distance Estimation==== |
|||
⚫ | The Mandelbrot set is represented |
||
The Mandelbrot set is printed with smooth colors. The ''e^(-|z|)-smoothing'', ''normalized iteration count'' and ''exterior distance estimation'' algorithms are used with NumPy and complex matrices (see Javier Barrallo & Damien M. Jones: [http://www.mi.sanu.ac.rs/vismath/javier/index.html ''Coloring Algorithms for Dynamical Systems in the Complex Plane''] and Mikael Hvidtfeldt Christensen: [http://blog.hvidtfeldts.net/index.php/2011/09/distance-estimated-3d-fractals-v-the-mandelbulb-different-de-approximations ''Distance Estimated 3D Fractals (V): The Mandelbulb & Different DE Approximations'']). |
|||
<syntaxhighlight lang="python">import numpy as np |
|||
import matplotlib.pyplot as plt |
|||
d, h = 800, 500 # pixel density (= image width) and image height |
|||
n, r = 200, 500 # number of iterations and escape radius (r > 2) |
|||
x = np.linspace(0, 2, num=d+1) |
|||
y = np.linspace(0, 2 * h / d, num=h+1) |
|||
A, B = np.meshgrid(x - 1, y - h / d) |
|||
C = 2.0 * (A + B * 1j) - 0.5 |
|||
Z, dZ = np.zeros_like(C), np.zeros_like(C) |
|||
D, S, T = np.zeros(C.shape), np.zeros(C.shape), np.zeros(C.shape) |
|||
for k in range(n): |
|||
M = abs(Z) < r |
|||
S[M], T[M] = S[M] + np.exp(- abs(Z[M])), T[M] + 1 |
|||
Z[M], dZ[M] = Z[M] ** 2 + C[M], 2 * Z[M] * dZ[M] + 1 |
|||
plt.imshow(S ** 0.1, cmap=plt.cm.twilight_shifted, origin="lower") |
|||
plt.savefig("Mandelbrot_set_1.png", dpi=200) |
|||
N = abs(Z) >= r # normalized iteration count |
|||
T[N] = T[N] - np.log2(np.log(np.abs(Z[N])) / np.log(r)) |
|||
plt.imshow(T ** 0.1, cmap=plt.cm.twilight_shifted, origin="lower") |
|||
plt.savefig("Mandelbrot_set_2.png", dpi=200) |
|||
N = abs(Z) > 2 # exterior distance estimation |
|||
D[N] = np.log(abs(Z[N])) * abs(Z[N]) / abs(dZ[N]) |
|||
plt.imshow(D ** 0.1, cmap=plt.cm.twilight_shifted, origin="lower") |
|||
plt.savefig("Mandelbrot_set_3.png", dpi=200)</syntaxhighlight> |
|||
====Normal Map Effect and Stripe Average Coloring==== |
|||
⚫ | The Mandelbrot set is represented using Normal Maps and Stripe Average Coloring by Jussi Härkönen (cf. Arnaud Chéritat: [https://www.math.univ-toulouse.fr/~cheritat/wiki-draw/index.php/Mandelbrot_set#Normal_map_effect ''Normal map effect'']). Note that the second derivative (ddZ) grows very fast, so the second method can only be used for small iteration numbers (n <= 400). See also [https://web.archive.org/web/20140618000747/http://jussiharkonen.com/files/on_fractal_coloring_techniques(lo-res).pdf ''On Smooth Fractal Coloring Techniques''] by Jussi Härkönen and [https://www.shadertoy.com/view/wtscDX Julia Stripes] on Shadertoy. |
||
<syntaxhighlight lang="python">import numpy as np |
<syntaxhighlight lang="python">import numpy as np |
||
import matplotlib.pyplot as plt |
import matplotlib.pyplot as plt |
||
Line 10,926: | Line 10,964: | ||
Z[M], dZ[M], ddZ[M] = Z[M] ** 2 + C[M], 2 * Z[M] * dZ[M] + 1, 2 * (dZ[M] ** 2 + Z[M] * ddZ[M]) |
Z[M], dZ[M], ddZ[M] = Z[M] ** 2 + C[M], 2 * Z[M] * dZ[M] + 1, 2 * (dZ[M] ** 2 + Z[M] * ddZ[M]) |
||
N = abs(Z) >= r # normal map effect 1 ( |
N = abs(Z) >= r # normal map effect 1 (equipotential lines) |
||
P, Q = S[N] / T[N], (S[N] + np.cos(stripes * np.angle(Z[N]))) / (T[N] + 1) |
P, Q = S[N] / T[N], (S[N] + np.cos(stripes * np.angle(Z[N]))) / (T[N] + 1) |
||
F = np.log2(np.log(np.abs(Z[N])) / np.log(r)) # fraction between 0 and 1 ( |
F = np.log2(np.log(np.abs(Z[N])) / np.log(r)) # fraction between 0 and 1 (normalization) |
||
R = Q + (P - Q) * F * F * (3 - 2 * F) # |
R = Q + (P - Q) * F * F * (3 - 2 * F) # interpolation (linear or Hermite) |
||
U, H = Z[N] / dZ[N], 1 + R / damping # normal vectors |
U, H = Z[N] / dZ[N], 1 + R / damping # normal vectors and height variations |
||
U, v = U / abs(U), np.exp(direction / 180 * np.pi * 1j) # unit |
U, v = U / abs(U), np.exp(direction / 180 * np.pi * 1j) # unit vectors |
||
D[N] = np.maximum((U.real * v.real + U.imag * v.imag + H * height) / (1 + height), 0) |
D[N] = np.maximum((U.real * v.real + U.imag * v.imag + H * height) / (1 + height), 0) |
||
Line 10,937: | Line 10,975: | ||
plt.savefig("Mandelbrot_normal_map_1.png", dpi=200) |
plt.savefig("Mandelbrot_normal_map_1.png", dpi=200) |
||
N = abs(Z) >= r # normal map effect 2 ( |
N = abs(Z) >= r # normal map effect 2 (equidistant lines) |
||
U = Z[N] * dZ[N] * ((1 + np.log(abs(Z[N]))) * np.conj(dZ[N] ** 2) - np.log(abs(Z[N])) * np.conj(Z[N] * ddZ[N])) |
U = Z[N] * dZ[N] * ((1 + np.log(abs(Z[N]))) * np.conj(dZ[N] ** 2) - np.log(abs(Z[N])) * np.conj(Z[N] * ddZ[N])) |
||
U, v = U / abs(U), np.exp(direction / 180 * np.pi * 1j) # unit |
U, v = U / abs(U), np.exp(direction / 180 * np.pi * 1j) # unit vectors |
||
D[N] = np.maximum((U.real * v.real + U.imag * v.imag + height) / (1 + height), 0) |
D[N] = np.maximum((U.real * v.real + U.imag * v.imag + height) / (1 + height), 0) |
||
Line 10,945: | Line 10,983: | ||
plt.savefig("Mandelbrot_normal_map_2.png", dpi=200)</syntaxhighlight> |
plt.savefig("Mandelbrot_normal_map_2.png", dpi=200)</syntaxhighlight> |
||
====Mercator Mandelbrot Maps and Zoom Images==== |
|||
A small change in the code above creates Mercator maps of the Mandelbrot set (see David Madore: [http://www.madore.org/~david/math/mandelbrot.html ''Mandelbrot set images and videos''] and Anders Sandberg: [https://www.flickr.com/photos/arenamontanus/sets/72157615740829949 ''Mercator Mandelbrot Maps'']). The maximum magnification is about <math>e ^ {2 \pi \cdot h / d} = e ^ {2 \pi \cdot 5.5} \approx 535.5 ^ {5.5} \approx 10 ^ {15}</math>, which is also the maximum for 64-bit arithmetic. Note that Anders Sandberg uses a different scaling. He uses <math>10 ^ {3 \cdot h / d} = 1000 ^ {h / d}</math> instead of <math>e ^ {2 \pi \cdot h / d} \approx 535.5 ^ {h / d}</math>, so his images appear somewhat compressed in comparison (but not much, because <math>1000 ^ {5.0} \approx 535.5 ^ {5.5}</math>). With the same pixel density and the same maximum magnification, the difference in height between the maps is only about 10 percent. By misusing a scatter plot, it is possible to create zoom images of the Mandelbrot set. See also [https://commons.wikimedia.org/wiki/File:Mandelbrot_sequence_new.gif ''Mandelbrot sequence new''] (Wikimedia) for a zoom animation to the given coordinates. |
A small change in the code above creates Mercator maps of the Mandelbrot set (see David Madore: [http://www.madore.org/~david/math/mandelbrot.html ''Mandelbrot set images and videos''] and Anders Sandberg: [https://www.flickr.com/photos/arenamontanus/sets/72157615740829949 ''Mercator Mandelbrot Maps'']). The maximum magnification is about <math>e ^ {2 \pi \cdot h / d} = e ^ {2 \pi \cdot 5.5} \approx 535.5 ^ {5.5} \approx 10 ^ {15}</math>, which is also the maximum for 64-bit arithmetic. Note that Anders Sandberg uses a different scaling. He uses <math>10 ^ {3 \cdot h / d} = 1000 ^ {h / d}</math> instead of <math>e ^ {2 \pi \cdot h / d} \approx 535.5 ^ {h / d}</math>, so his images appear somewhat compressed in comparison (but not much, because <math>1000 ^ {5.0} \approx 535.5 ^ {5.5}</math>). With the same pixel density and the same maximum magnification, the difference in height between the maps is only about 10 percent. By misusing a scatter plot, it is possible to create zoom images of the Mandelbrot set. See also [https://commons.wikimedia.org/wiki/File:Mandelbrot_sequence_new.gif ''Mandelbrot sequence new''] (Wikimedia) for a zoom animation to the given coordinates. |
||
<syntaxhighlight lang="python">import numpy as np |
<syntaxhighlight lang="python">import numpy as np |
||
Line 10,984: | Line 11,023: | ||
plt.savefig("Mercator_Mandelbrot_zoom.png", dpi=100)</syntaxhighlight> |
plt.savefig("Mercator_Mandelbrot_zoom.png", dpi=100)</syntaxhighlight> |
||
====Perturbation Theory and Deep Mercator Maps==== |
|||
For deep zoom images it is sufficient to calculate a single point with high accuracy. A good approximation can then be found for all other points by means of a perturbation calculation with standard accuracy. Rebasing is used to reduce glitches. See [https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set#Perturbation_theory_and_series_approximation Perturbation theory] (Wikipedia) and [https://gbillotey.github.io/Fractalshades-doc/math.html#avoiding-loss-of-precision Avoiding loss of precision] (Fractalshades) for details. See also the image [https://www.flickr.com/photos/arenamontanus/3430921497/in/album-72157615740829949/ Deeper Mercator Mandelbrot] by Anders Sandberg. |
For deep zoom images it is sufficient to calculate a single point with high accuracy. A good approximation can then be found for all other points by means of a perturbation calculation with standard accuracy. Rebasing is used to reduce glitches. See [https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set#Perturbation_theory_and_series_approximation Perturbation theory] (Wikipedia) and [https://gbillotey.github.io/Fractalshades-doc/math.html#avoiding-loss-of-precision Avoiding loss of precision] (Fractalshades) for details. See also the image [https://www.flickr.com/photos/arenamontanus/3430921497/in/album-72157615740829949/ Deeper Mercator Mandelbrot] by Anders Sandberg. |
||
<syntaxhighlight lang="python">import numpy as np |
<syntaxhighlight lang="python">import numpy as np |