pythonnumpymatplotlibmandelbrot

mandelbrot set gets blurry at around 2^47 zoom


So i created a simple mandelbrot zoom code that zooms in(lmb) or out(rmb) where you click. The portion that is render is halved every click as it zooms in the curve.

The problem is no matter how large the maxiter and additer count is, the fractal always seems to get blurry at around 2^47 zoom value. Here is what it looks like at 2^49 zoom.

Anyways below is my code. Can someone please tell me why it gets blurry and how i can solve this issue?

import numpy as np
from numba import jit
from matplotlib import pyplot as plt
from matplotlib import colors
from datetime import datetime


width, height, resolution = 8, 8, 100
maxiter = 50    #starting maxiter
additer = 50    #value to add to maxiter upon zoom
xmin, xmax = -2, 1
ymin, ymax = -1.5, 1.5
zoom = 1
color = 'terrain'   #https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html


#--------------------------------------------------------------------------------------------------#

@jit
def mandel(c, maxiter):
    z = c
    for n in range(maxiter):
        if (z.real*z.real + z.imag*z.imag)>4:   #if z diverges
            return n
        z = (z * z) + c
    return 0



@jit
def mandelbrot(xmin, xmax, ymin, ymax, maxiter):
    re = np.linspace(xmin, xmax, width*resolution)
    im = np.linspace(ymin, ymax, height*resolution)
    answer = np.empty((width*resolution, height*resolution))
    for y in range(height*resolution):
        for x in range(width*resolution):
            answer[x,y] = mandel(re[x] + 1j*im[y], maxiter) #iteration count at (re, im)
    return answer



def onclick(event):
    global zoom, xmax, xmin, ymax, ymin, maxiter
    if event.button == 1:
        zoom *= 2
        maxiter += additer
    elif event.button == 3:
        zoom/=2
        if maxiter > additer:
            maxiter -= additer
    #get the re,im coordinate position of mouse
    posx = xmin + (xmax-xmin)*event.xdata/(width*resolution)
    posy = ymin + (ymax-ymin)*event.ydata/(height*resolution)
    print(posx, posy, "iter:", maxiter, "zoom:", zoom)
    #change max and min so the coordinate is at the center
    xmin = posx - (1.5/zoom)
    xmax = posx + (1.5/zoom)
    ymin = posy - (1.5/zoom)
    ymax = posy + (1.5/zoom)
    #recalculate and display
    answer = mandelbrot(xmin, xmax, ymin, ymax, maxiter)
    plt.imshow(answer.T, cmap=color)
    plt.draw()

#for benchmarking
'''a = datetime.now()
mandelbrot(xmin, xmax, ymin, ymax, zoom)
print(datetime.now() - a)'''

answer = mandelbrot(xmin, xmax, ymin, ymax, maxiter)
plt.connect('button_press_event', onclick)
plt.imshow(answer.T, cmap=color)
plt.axis('off')
plt.tight_layout()
plt.show()

Solution

  • Below a 2^-47 image width and for most of the areas where you would like to zoom, the difference between 2 adjacent pixels - considering 1000 pixels width - will yield 0 at the standard double precision.

    To go deeper you will need to compute at least one point (reference orbit) with a multi-precision arithmetic package (Decimal, gmpy2). For the remaining pixels, in order to keep the calculation time acceptable you can usually iterate only the delta - with a few caveats that are beyond the scope of this question.

    For an example of such implementation using python / numpy you can have a look at package a like fractalshades. The gallery section of the documentation features examples of deep-zooms down to 10^-2000 for Mandelbrot and Burning Ship: https://gbillotey.github.io/Fractalshades/examples/index.html

    Disclaimer: I am the author of fractalshades.