Skip to content

Commit 1e5e79c

Browse files
Add a basic mandelbrot implementation.
1 parent 826f88e commit 1e5e79c

File tree

5 files changed

+1229
-0
lines changed

5 files changed

+1229
-0
lines changed

mandelbrot-script.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
assert __name__ == '__main__'
2+
from mandelbrot.__main__ import parse_args, main
3+
kwargs = parse_args()
4+
main(**kwargs)

mandelbrot/__init__.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Inspired by: https://realpython.com/mandelbrot-set-python/
2+
3+
from math import log
4+
import os.path
5+
6+
7+
IMPORT_PATH_ENTRY = os.path.dirname(os.path.dirname(__file__))
8+
9+
BOUNDS = (-2, 0.5, -1.5, 1.5)
10+
11+
12+
def xy_to_complex(x, y):
13+
return x + y * 1j
14+
15+
16+
def iter_xy_to_complex(points):
17+
for x, y in points:
18+
yield x + y * 1j
19+
20+
21+
def apply_to_gridarray(mbs, grid, concurrent=None):
22+
if concurrent is None:
23+
values = grid.values
24+
for i, p in enumerate(grid.iter_points()):
25+
values[i] = mbs.stability_xy(*p)
26+
else:
27+
concurrent(mbs, grid)
28+
29+
30+
class MandelbrotSet:
31+
32+
def __init__(self, maxiterations, *, escape_radius=2.0, clamp=True):
33+
self.maxiterations = maxiterations
34+
self.escape_radius = escape_radius
35+
self.clamp = clamp
36+
37+
def __contains__(self, c):
38+
return self.stability(c, clamp=True) == 1
39+
40+
def escape_count(self, c, smooth=False):
41+
z = 0
42+
for iteration in range(self.maxiterations):
43+
z = z ** 2 + c
44+
if abs(z) > self.escape_radius:
45+
if smooth:
46+
return iteration + 1 - log(log(abs(z))) / log(2)
47+
return iteration
48+
return self.maxiterations
49+
50+
def stability(self, c, smooth=None, clamp=None):
51+
if clamp is None:
52+
clamp = smooth if smooth is not None else self.clamp
53+
if smooth is None:
54+
smooth = False
55+
value = self.escape_count(c, smooth) / self.maxiterations
56+
return max(0.0, min(value, 1.0)) if clamp else value
57+
58+
def stability_xy(self, x, y, smooth=None, clamp=None):
59+
c = x + y * 1j
60+
return self.stability(c, smooth, clamp)
61+
62+
def map_xy(self, points, smooth=None, clamp=None):
63+
for p in points:
64+
x, y = p
65+
c = x + y * 1j
66+
yield p, self.stability_xy(*p, smooth, clamp)
67+
68+
69+
#############################
70+
# aliases
71+
72+
from ._xypoints import (
73+
GridSpec,
74+
GridArray, BasicGridArray,
75+
MultiprocessingGridArray, BasicMultiprocessingGridArray,
76+
)

mandelbrot/__main__.py

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import argparse
2+
import array
3+
import sys
4+
5+
from . import BOUNDS, MandelbrotSet, apply_to_gridarray
6+
from ._xypoints import GridSpec, BasicGridArray, BasicMultiprocessingGridArray
7+
from ._concurrency import CONCURRENCY
8+
9+
10+
def _get_grid(spec, concurrentname):
11+
if concurrentname == 'multiprocessing-not-shared':
12+
return BasicGridArray.from_spec(spec)
13+
if 'multiprocessing' in concurrentname:
14+
return BasicMultiprocessingGridArray.from_spec(spec)
15+
else:
16+
return BasicGridArray.from_spec(spec)
17+
18+
19+
def prep(maxiterations, density, concurrent=None):
20+
mbs = MandelbrotSet(maxiterations)
21+
gridspec = GridSpec(*BOUNDS, density)
22+
grid = _get_grid(gridspec, getattr(concurrent, '_concurrent', ''))
23+
return mbs, grid
24+
25+
26+
#######################################
27+
# output
28+
29+
def output_text(values, width):
30+
hit = ' *'
31+
hit = '\u26aa'
32+
#hit = '\U0001f7e1'
33+
miss = ' '
34+
35+
def iter_lines():
36+
for rowi in range(width):
37+
line = ''
38+
for i in range(rowi*width, rowi*width+width):
39+
if values[i] == 1:
40+
line += hit
41+
else:
42+
line += miss
43+
yield line
44+
45+
print('*'*(width*2+4))
46+
print('*', ' '*(width*2), '*')
47+
for line in iter_lines():
48+
print('*', line, '*')
49+
print('*', ' '*(width*2), '*')
50+
print('*'*(width*2+4))
51+
52+
53+
def output_image(values, width):
54+
from PIL import Image
55+
56+
height = width
57+
BLACK_AND_WHITE = "1"
58+
59+
image = Image.new(mode=BLACK_AND_WHITE, size=(width, height))
60+
image.putdata(values)
61+
image.show()
62+
63+
64+
OUTPUT = {
65+
'stdout': output_text,
66+
'image': output_image,
67+
}
68+
69+
70+
#######################################
71+
# the script
72+
73+
'''
74+
(with GIL)
75+
size: 100/100
76+
77+
threads-not-shared 86s
78+
threads-shared-direct 105s
79+
threads-shared-executor 93s
80+
81+
interpreters-shared-pipe ---
82+
interpreters-not-shared-pipe 28s
83+
interpreters-shared-channel 19s
84+
interpreters-not-shared-channel 19s
85+
86+
multiprocessing-not-shared 19s
87+
multiprocessing-shared-direct 24s
88+
multiprocessing-shared-executor ---
89+
90+
async 76s
91+
'''
92+
93+
def parse_args(argv=sys.argv[1:], prog=sys.argv[0]):
94+
parser = argparse.ArgumentParser(prog=prog)
95+
96+
parser.add_argument('--maxiterations', type=int)
97+
parser.add_argument('--density', type=int)
98+
parser.add_argument('--output', choices=list(OUTPUT))
99+
parser.add_argument('--concurrent', choices=list(CONCURRENCY))
100+
101+
args = parser.parse_args(argv)
102+
ns = vars(args)
103+
104+
if args.concurrent is not None:
105+
args.concurrent = CONCURRENCY[args.concurrent]
106+
107+
for key, value in list(ns.items()):
108+
if value is None:
109+
del ns[key]
110+
111+
return ns
112+
113+
114+
def main(maxiterations=100, density=None, output='stdout', concurrent=None):
115+
if not density:
116+
if output == 'stdout':
117+
density = 80
118+
else:
119+
density = 512
120+
121+
try:
122+
output = OUTPUT[output]
123+
except KeyError:
124+
raise ValueError(f'unsupported output {output!r}')
125+
126+
mbs, grid = prep(maxiterations, density, concurrent)
127+
apply_to_gridarray(mbs, grid, concurrent)
128+
output(grid.values, grid.density)
129+
130+
131+
if __name__ == '__main__':
132+
kwargs = parse_args()
133+
main(**kwargs)

0 commit comments

Comments
 (0)