Welcome, guest | Sign In | My Account | Store | Cart

This is a mini-framework for exploring the Mandelbrot Set.

It outputs to a Tkinter window or to a PNG file (Pillow package required). It includes a disk caching mechanism for saving and later retrieving Mandelbrot calculations to avoid recalculating the Set on each run.

See module documentation for further information. Compatible with Python 2 and 3.

Python, 248 lines
 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248``` ```""" recipe_mandelbrot.py A mini-framework for exploring the Mandelbrot set. The Mandelbrot Set is the set of points which when iterated over by a particular math procedure repeatedly, the cumulative value does not go to infinity. There are more complete explanations elsewhere. Suffice it to say that the output of calc_mandelbrot_set() returns an array of values for how quickly the point escaped to infinity, which has been proven equivalent to the absolute value of the cumulative value > 2. If the point does not escape, its escape value equals the maximum number of iterations. The higher the number of iterations, the more accurate the output of calc_mandelbrot_values(). Strictly speaking the Mandelbrot Set, as calculated here, is only the points whose escape values equal the maximum number of iterations. Rendering those points as black presents the standard bug-shaped image. The spectacular colored Mandelbrot images are created by mapping all the escape values to various colors. There any number of color mapping schemes. The art of exploring the Mandelbrot Set visually is choosing or creating a mapping which works with the region of the Set one has calculated. (The smaller the region, the higher the number of iterations should be used.) In this recipe escapevals_to_color() provides one such mapping. Change that function to change the color mapping. Since it is time-consuming to calculate the Mandelbrot escape values, it's convenient to cache the calculations to disk, then use the cached values as one experiments with color mappings. This recipe contains a disk caching mechanism which saves the calculations from one run to a file. The parameters of those calculations are encoded in the filename, so the next time those particular calculations are required, the recipe will find those calculations automatically. The recipe outputs to a PNG file and to a Tkinter window. The PNG file output requires installation of the Pillow version of the Python Imaging Library. Suggestions: * Run the recipe as is to see the output in Tkinter. * Experiment by varying the number of iterations (MAX_ITERS). * Experiment by varying the graph rectangle (X_MIN, X_MAX, Y_MIN, Y_MAX). * Experiment by changing the color mapping function (escapeval_to_color). """ __author__ = "Jack Trainor" __date__ = "2015-12-28" import sys import re import os.path import time import array ######################################################################## """ Customize these constants IMG_WD/IMG_HT should be set roughly equal to (X_MAX-XMIN)/(Y_MAX-Y_MIN). """ MAX_ITERS = 250 IMG_WD = 400 IMG_HT = 400 X_MIN = -2.125 X_MAX = 0.875 Y_MIN = -1.5 Y_MAX = 1.5 OUTPUT_DIR = "C:/" DISK_CACHING = True ######################################################################## def calc_mandelbrot_vals(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): escapevals = [] xwd = xmax - xmin yht = ymax - ymin for y in range(imght): for x in range(imgwd): z = 0 r = xmin + xwd * x / imgwd i = ymin + yht * y / imght c = complex(r, i) for n in range(maxiters + 1): z = z*z + c if abs(z) > 2.0: # escape radius break escapevals.append(n) return escapevals ######################################################################## def escapeval_to_color(n, maxiters): """ http://www.fractalforums.com/index.php?topic=643.msg3522#msg3522 """ v = float(n) / float(maxiters) n = int(v * 4096.0) r = g = b = 0 if (n == maxiters): pass elif (n < 64): r = n * 2 elif (n < 128): r = (((n - 64) * 128) / 126) + 128 elif (n < 256): r = (((n - 128) * 62) / 127) + 193 elif (n < 512): r = 255 g = (((n - 256) * 62) / 255) + 1 elif (n < 1024): r = 255 g = (((n - 512) * 63) / 511) + 64 elif (n < 2048): r = 255 g = (((n - 1024) * 63) / 1023) + 128 elif (n < 4096): r = 255 g = (((n - 2048) * 63) / 2047) + 192 return (int(r), int(g), int(b)) ######################################################################## def get_mb_corename(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): return "mb_%d_wd_%d_ht_%d_xa_%f_xb_%f_ya_%f_yb_%f_" % (maxiters, imgwd, imght, xmin, xmax, ymin, ymax) def extract_mb_filename(filename): maxiters = xmin = xmax = ymin = ymax = imgwd = imght = "" filename_re = re.compile("mb_(.+)_wd_(.+)_ht_(.+)_xa_(.+)_xb_(.+)_ya_(.+)_yb_(.+)_.*") match = filename_re.match(filename) if match: maxiters = int(match.group(1)) imgwd = int(match.group(2)) imght = int(match.group(3)) xmin = float(match.group(4)) xmax = float(match.group(5)) ymin = float(match.group(6)) ymax = float(match.group(7)) return maxiters, xmin, xmax, ymin, ymax, imgwd, imght ######################################################################## def write_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): escapevals = calc_mandelbrot_vals(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) path = get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) write_array_file(path, escapevals, 'i') def read_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): path = get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) array_ = read_array_file(path, 'i', imght*imgwd) return array_ def get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): corename = get_mb_corename(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) path = os.path.join(OUTPUT_DIR, corename+".data") return path ######################################################################## def write_array_file(path, list_, typecode): array_ = array.array(typecode, list_) f = open(path, "wb") array_.tofile(f) f.close() def read_array_file(path, typecode, count): f = open(path, 'rb') array_ = array.array(typecode) array_.fromfile(f, count) return array_ ######################################################################## def get_mandelbrot(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): if DISK_CACHING: path = get_mb_path(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) if not os.path.exists(path): write_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) return read_mb(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) else: return calc_mandelbrot_vals(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) ######################################################################## def mb_to_png(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): try: from PIL import Image from PIL import ImageDraw array_ = get_mandelbrot(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) img = Image.new("RGB", (imgwd, imght)) d = ImageDraw.Draw(img) i = 0 for y in range(imght): for x in range(imgwd): n = array_[i] color = escapeval_to_color(n, maxiters) d.point((x, y), fill=color) i += 1 del d corename = get_mb_corename(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) path = os.path.join(OUTPUT_DIR, corename+".png") img.save(path) except ImportError: println("mb_to_png failed: Pillow not installed.") ######################################################################## def mb_to_tkinter(maxiters, xmin, xmax, ymin, ymax, imgwd, imght): try: import tkinter as tk except ImportError: import Tkinter as tk array_ = get_mandelbrot(maxiters, xmin, xmax, ymin, ymax, imgwd, imght) window = tk.Tk() canvas = tk.Canvas(window, width=imgwd, height=imght, bg="#000000") img = tk.PhotoImage(width=imgwd, height=imght) canvas.create_image((0, 0), image=img, state="normal", anchor=tk.NW) i = 0 for y in range(imght): for x in range(imgwd): n = array_[i] color = escapeval_to_color(n, maxiters) r = hex(color[0])[2:].zfill(2) g = hex(color[1])[2:].zfill(2) b = hex(color[2])[2:].zfill(2) img.put("#" + r + g + b, (x, y)) i += 1 println("mb_to_tkinter %s" % time.asctime()) canvas.pack() tk.mainloop() ######################################################################## def main(): println("Start %s" % time.asctime()) mb_to_png(MAX_ITERS, X_MIN, X_MAX, Y_MIN, Y_MAX, IMG_WD, IMG_HT) println("mb_to_png %s" % time.asctime()) mb_to_tkinter(MAX_ITERS, X_MIN, X_MAX, Y_MIN, Y_MAX, IMG_WD, IMG_HT) ######################################################################## def println(text): sys.stdout.write(text + "\n") if __name__ == "__main__": main() ```

There are many programming recipes available online for the Mandelbrot Set. My intention here is to provide a simple framework for understanding and exploring the Mandelbrot Set in a single Python module.

Most sample Mandelbrot code is a big loop through (x,y) pairs to calculate the Mandelbrot escape value, then map that value to a color, then output that color an image on the screen or to a file.

I've decoupled the Mandelbrot calculation from the coloring and output to avoid the same, usually lengthy, recalculations. I've added a disk caching mechanism so Mandelbrot values previously calculated can be easily reused.

This frees the programmer to focus on the main issues when producing a Mandelbrot image -- choosing a section of the Set to calculate, to how many iterations, and the color mapping (see escapeval_to_color function) for viewing the resulting image.

 Created by Jack Trainor on Mon, 28 Dec 2015 (MIT)