I wrote some code to render the Mandelbrot set with continuous coloring, but the bulbs on the period-2 blub are not colored correctly. regions that should not escape are colored as though the escape after 100 iterations, which happens to be my iteration limit.
What I tried
1. Increasing resolution and max iteration count
2. Increasing precision (np.float32 -> np.float64). Unfortunately, I could not finish execution because of the time complexity.
import numpy as np
import matplotlib.pyplot as plt
def mandelbrot(cmax, width, height, maxiter):
real = np.linspace(-2, 0.5, width, dtype=np.float32)
imag = np.linspace(0, cmax.imag, height // 2 + 1, dtype=np.float32)
c_real, c_imag = np.meshgrid(real, imag)
output = np.zeros((height // 2 + 1, width), dtype=np.uint16)
z_real = np.zeros_like(c_real)
z_imag = np.zeros_like(c_imag)
mask = np.ones_like(c_real, dtype=bool)
p = np.sqrt((c_real - 0.25)**2 + c_imag**2)
cardioid = c_real < p - 2 * p**2 + 0.25
period2_bulb = (c_real + 1)**2 + c_imag**2 < 0.0625
mask[cardioid | period2_bulb] = False
output[~mask] = maxiter - 1
epsilon = 1e-10
for i in range(maxiter):
zr2 = z_real[mask] ** 2
zi2 = z_imag[mask] ** 2
z_imag_new = 2 * z_real[mask] * z_imag[mask] + c_imag[mask]
z_real[mask] = zr2 - zi2 + c_real[mask]
z_imag[mask] = z_imag_new
diverged = zr2 + zi2 >= 4.0
abs_z = zr2 + zi2 + epsilon
output[mask] = i + 1 - np.log(np.log(abs_z)) / np.log(2)
mask[mask] = ~diverged
output[output == maxiter - 1] = 0
full_output = np.vstack([np.flipud(output), np.flipud(output[-2::-1, :])])
return full_output
width, height, maxiter = 12000, 9000, 100
cmax = complex(0, 1)
mandelbrot_set = mandelbrot(cmax, width, height, maxiter)
plt.figure(figsize=(16, 12))
plt.imshow(mandelbrot_set, extent=[-2, 0.5, -1, 1], cmap="inferno", interpolation="bilinear")
plt.colorbar(label="Iterations to Divergence")
plt.title("Mandelbrot Set")
plt.xlabel("Re(c)")
plt.ylabel("Im(c)")
plt.show()
I wrote some code to render the Mandelbrot set with continuous coloring, but the bulbs on the period-2 blub are not colored correctly. regions that should not escape are colored as though the escape after 100 iterations, which happens to be my iteration limit.
What I tried
1. Increasing resolution and max iteration count
2. Increasing precision (np.float32 -> np.float64). Unfortunately, I could not finish execution because of the time complexity.
import numpy as np
import matplotlib.pyplot as plt
def mandelbrot(cmax, width, height, maxiter):
real = np.linspace(-2, 0.5, width, dtype=np.float32)
imag = np.linspace(0, cmax.imag, height // 2 + 1, dtype=np.float32)
c_real, c_imag = np.meshgrid(real, imag)
output = np.zeros((height // 2 + 1, width), dtype=np.uint16)
z_real = np.zeros_like(c_real)
z_imag = np.zeros_like(c_imag)
mask = np.ones_like(c_real, dtype=bool)
p = np.sqrt((c_real - 0.25)**2 + c_imag**2)
cardioid = c_real < p - 2 * p**2 + 0.25
period2_bulb = (c_real + 1)**2 + c_imag**2 < 0.0625
mask[cardioid | period2_bulb] = False
output[~mask] = maxiter - 1
epsilon = 1e-10
for i in range(maxiter):
zr2 = z_real[mask] ** 2
zi2 = z_imag[mask] ** 2
z_imag_new = 2 * z_real[mask] * z_imag[mask] + c_imag[mask]
z_real[mask] = zr2 - zi2 + c_real[mask]
z_imag[mask] = z_imag_new
diverged = zr2 + zi2 >= 4.0
abs_z = zr2 + zi2 + epsilon
output[mask] = i + 1 - np.log(np.log(abs_z)) / np.log(2)
mask[mask] = ~diverged
output[output == maxiter - 1] = 0
full_output = np.vstack([np.flipud(output), np.flipud(output[-2::-1, :])])
return full_output
width, height, maxiter = 12000, 9000, 100
cmax = complex(0, 1)
mandelbrot_set = mandelbrot(cmax, width, height, maxiter)
plt.figure(figsize=(16, 12))
plt.imshow(mandelbrot_set, extent=[-2, 0.5, -1, 1], cmap="inferno", interpolation="bilinear")
plt.colorbar(label="Iterations to Divergence")
plt.title("Mandelbrot Set")
plt.xlabel("Re(c)")
plt.ylabel("Im(c)")
plt.show()
Share
Improve this question
edited Mar 24 at 22:41
Sandipan Dey
23.3k4 gold badges57 silver badges71 bronze badges
asked Mar 22 at 21:36
F-22 DestroyerF-22 Destroyer
838 bronze badges
9
|
Show 4 more comments
1 Answer
Reset to default 3The updating of the output and the mask inside the loop (iterations) is incorrect. We need to focus only on the points inside mask and escaped in current iteration.
def mandelbrot(cmax, width, height, maxiter):
real = np.linspace(-2, 0.5, width, dtype=np.float32)
imag = np.linspace(0, cmax.imag, height // 2 + 1, dtype=np.float32)
c_real, c_imag = np.meshgrid(real, imag)
output = np.zeros((height // 2 + 1, width), dtype=np.uint16)
z_real = np.zeros_like(c_real)
z_imag = np.zeros_like(c_imag)
mask = np.ones_like(c_real, dtype=bool)
#p = np.sqrt((c_real - 0.25)**2 + c_imag**2)
#cardioid = c_real < p - 2 * p**2 + 0.25
q = (c_real - 0.25)**2 + c_imag**2
cardioid = q*(q + c_real - 1/4) <= 1/4*c_imag**2
period2_bulb = (c_real + 1)**2 + c_imag**2 < 1/16 #0.0625
mask[cardioid | period2_bulb] = False # these points never escape
epsilon = 1e-10
for i in range(maxiter):
z_imag_new = 2 * z_real[mask] * z_imag[mask] + c_imag[mask]
z_real[mask] = z_real[mask]**2 - z_imag[mask]**2 + c_real[mask]
z_imag[mask] = z_imag_new
diverged = z_real**2 + z_imag**2 >= 4.0 # escaped in this iteration
abs_z = z_real[mask & diverged]**2 + z_imag[mask & diverged]**2 + epsilon
output[mask & diverged] = i + 1 - np.log(np.log(abs_z)) / np.log(2) # only the points inside mask that escaped in the current iteration are need to be updated in the output
mask[mask & diverged] = False # only the escaped points inside the mask are no longer needed to be considered
#output[output == maxiter - 1] = 0
full_output = np.vstack([np.flipud(output), np.flipud(output[-2::-1, :])])
return full_output
Although this may not be a very efficient implementation and for more efficient implementations one could use numba
jit
, as suggested here.
mask
seems to be both a marker of what has diverged and what will never (or, to be accurate, the opposite). Which seems to me to be a complex intrication motivated by your colormap objective. But, well, that doesn't explain the problem, I think. Just, it makes it harder to think about it. And it seems to me that it would be better to 1st compute the "escape iteration", and the to deal with rendering afterward. – chrslg Commented Mar 23 at 14:21- np.log(np.log(abs_z)) / np.log(2)
. Since,abs_z
is probably very small (otherwise it would have diverged before) almost everywhere (but on the boundary), thatlog(log())
is probably negative. So you end up with values greater thanmaxiter
. And combine with my first remark, that means that after 100 iterations, everything is probably> maxiter
– chrslg Commented Mar 23 at 14:34output[output==maxiter-1]=0
– chrslg Commented Mar 23 at 14:42