Skip to content

Commit fc4c3f5

Browse files
rik404ritvikrao
andauthored
"Ray mandelbrot" (#291)
* "Ray mandelbrot" * Added readme and test * fix mandelbrot path in test * include matplotlib --------- Co-authored-by: Ritvik Rao <rsrao2@illinois.edu>
1 parent a043387 commit fc4c3f5

File tree

5 files changed

+92
-1
lines changed

5 files changed

+92
-1
lines changed

.github/workflows/charm4py.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
python-version: ${{ matrix.python-version }}
3232
- name: Install dependencies
3333
run: |
34-
pip install setuptools cython cffi greenlet numpy torch torchvision filelock
34+
pip install setuptools cython cffi greenlet numpy torch torchvision filelock matplotlib
3535
if [ ${{ matrix.os }} == 'macos-13' ]; then
3636
# pypi only distributes torch packages w/ numpy v1 for macos-x86_64
3737
pip install 'numpy<2'

examples/ray/mandelbrot/README.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
==================
2+
Mandelbrot example
3+
==================
4+
5+
mandelbrot.py <height> <width> <max_iter> <tile_size>
6+
- height - height of image
7+
- width - width of image
8+
- max_iter - determines the "defintion" of zoom
9+
- tile_size - parallelization factor. each tile is computed in parallel
10+
11+
Generates mandelbrot.png - Computes a high-resolution image of the Mandelbrot set using parallel processing. It divides the image into smaller rectangular tiles and distributes these tiles across multiple worker tasks using Ray-Charm4py. Each task calculates whether each pixel in its tile belongs to the Mandelbrot set, storing the result in a memory-mapped file to avoid using excessive RAM. This approach enables efficient generation of very large fractal images by balancing performance and memory usage.
12+
13+
.. image:: mandelbrot_ray.png
14+
:alt: Mandelbrot Set Visualization
15+
:align: center
16+

examples/ray/mandelbrot/mandelbrot.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from charm4py import ray, charm
2+
import numpy as np
3+
import matplotlib.pyplot as plt
4+
import os
5+
6+
# Compute whether a point is in the Mandelbrot set
7+
def mandelbrot_fast(re, im, max_iter):
8+
zr = zi = 0.0
9+
for i in range(max_iter):
10+
zr2 = zr * zr
11+
zi2 = zi * zi
12+
if zr2 + zi2 > 4.0:
13+
return i
14+
zi = 2 * zr * zi + im
15+
zr = zr2 - zi2 + re
16+
return max_iter
17+
18+
# Remote task to compute a tile
19+
@ray.remote
20+
def compute_tile(x_start, x_end, y_start, y_end, width, height, max_iter):
21+
tile = np.zeros((y_end - y_start, x_end - x_start), dtype=np.uint16)
22+
for y in range(y_start, y_end):
23+
for x in range(x_start, x_end):
24+
re = 3.5 * (x / width) - 2.5
25+
im = 2.0 * (y / height) - 1.0
26+
tile[y - y_start, x - x_start] = mandelbrot_fast(re, im, max_iter)
27+
return tile
28+
29+
def generate_mandelbrot_image_optimized(width=12000, height=8000, max_iter=200, tile_size=1000, max_pending=1000):
30+
# Pre-create the empty file with the correct size
31+
total_bytes = 2 * width * height # 2 bytes per pixel (uint16)
32+
with open("output/mandelbrot_large.dat", "wb") as f:
33+
f.seek(total_bytes - 1)
34+
f.write(b'\0')
35+
result_image = np.memmap("output/mandelbrot_large.dat", dtype=np.uint16, mode='w+', shape=(height, width))
36+
pending = []
37+
38+
for y in range(0, height, tile_size):
39+
for x in range(0, width, tile_size):
40+
x_end = min(x + tile_size, width)
41+
y_end = min(y + tile_size, height)
42+
tile_ref = compute_tile.remote(x, x_end, y, y_end, width, height, max_iter)
43+
pending.append(((x, y), tile_ref))
44+
45+
if len(pending) >= max_pending:
46+
(x0, y0), tile = pending.pop(0)
47+
tile = ray.get(tile)
48+
result_image[y0:y0+tile.shape[0], x0:x0+tile.shape[1]] = tile
49+
50+
for (x0, y0), tile_ref in pending:
51+
tile = ray.get(tile_ref)
52+
result_image[y0:y0+tile.shape[0], x0:x0+tile.shape[1]] = tile
53+
54+
return result_image
55+
56+
57+
def main(args):
58+
output_path = "output/mandelbrot_large.dat"
59+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
60+
ray.init()
61+
# Run the benchmark
62+
image = generate_mandelbrot_image_optimized(width=int(args[1]), height=int(args[2]), max_iter=int(args[3]), tile_size=int(args[4]))
63+
# Optional: show the result
64+
plt.imshow(image, cmap='hot')
65+
plt.title("Mandelbrot Set (Ray)")
66+
plt.axis('off')
67+
plt.savefig("mandelbrot_ray.png", dpi=300, bbox_inches='tight')
68+
os.remove('output/mandelbrot_large.dat')
69+
charm.exit()
70+
71+
charm.start(main)
219 KB
Loading

test_config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,5 +312,9 @@
312312
"condition": "numbaInstalled",
313313
"path": "examples/wave2d/wave2d.py",
314314
"args": "300 -1 --NO-RENDER"
315+
},
316+
{
317+
"path": "examples/ray/mandelbrot/mandelbrot.py",
318+
"args": "100 100 10 10"
315319
}
316320
]

0 commit comments

Comments
 (0)