Skip to content

Instantly share code, notes, and snippets.

@fxn
Last active August 23, 2021 15:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fxn/61b2c0226cd60c20161b55725185bf2c to your computer and use it in GitHub Desktop.
Save fxn/61b2c0226cd60c20161b55725185bf2c to your computer and use it in GitHub Desktop.
Mandelbrot set generator from "Programming Rust", with ports to Crystal and Ruby.
// Mandelbrot set generator from "Programming Rust", with I/O removed (the
// original code writes a PNG file).
use std::env;
use std::str::FromStr;
use num::Complex;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 5 {
eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]);
eprintln!("Example: {} mandel.png 1000x750 -1.20,0.35 -1,0.20", args[0]);
std::process::exit(1);
}
let bounds = parse_pair(&args[2], 'x').expect("error parsing image dimensions");
let upper_left = parse_complex(&args[3]).expect("error parsing upper left corner point");
let lower_right = parse_complex(&args[4]).expect("error parsing lower right corner point");
let mut pixels = vec![0; bounds.0 * bounds.1];
render(&mut pixels, bounds, upper_left, lower_right);
}
fn parse_complex(s: &str) -> Option<Complex<f64>> {
match parse_pair(s, ',') {
Some((re, im)) => Some(Complex { re, im }),
None => None
}
}
fn parse_pair<T: FromStr>(s: &str, separator: char) -> Option<(T, T)> {
match s.find(separator) {
None => None,
Some(index) => {
match (T::from_str(&s[..index]), T::from_str(&s[index + 1..])) {
(Ok(l), Ok(r)) => Some((l, r)),
_ => None
}
}
}
}
fn pixel_to_point(bounds: (usize, usize), pixel: (usize, usize), upper_left: Complex<f64>, lower_right: Complex<f64>) -> Complex<f64> {
let (width, height) = (lower_right.re - upper_left.re, upper_left.im - lower_right.im);
Complex {
re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64,
im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64
}
}
fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {
let mut z = Complex { re: 0.0, im: 0.0 };
for i in 0..limit {
if z.norm_sqr() > 4.0 {
return Some(i);
}
z = z * z + c;
}
None
}
fn render(pixels: &mut [u8], bounds: (usize, usize), upper_left: Complex<f64>, lower_right: Complex<f64>) {
assert!(pixels.len() == bounds.0 * bounds.1);
for row in 0..bounds.1 {
for column in 0..bounds.0 {
let point = pixel_to_point(bounds, (column, row), upper_left, lower_right);
pixels[row * bounds.0 + column] = match escape_time(point, 255) {
None => 0,
Some(count) => 255 - count as u8
};
}
}
}
# Port of the Rust code for benchmarking purposes, does not mimick error
# handling because it wouldn't make a difference.
require "complex"
class MandelbrotGenerator
def generate
bounds = ARGV[1].split('x').map(&.to_i)
upper_left = parse_complex(ARGV[2])
lower_right = parse_complex(ARGV[3])
pixels = Array.new(value: 0, size: bounds[0] * bounds[1])
render(pixels, bounds, upper_left, lower_right)
end
private def parse_complex(str : String) : Complex
real, imag = str.split(',').map(&.to_f)
Complex.new(real: real, imag: imag)
end
private def escape_time(c : Complex, limit : Int32) : Int32?
# We could save one iteration, but let's do it like it is done in the book.
z = Complex.zero
(0...limit).each do |i|
return i if z.abs2 > 4
z = z * z + c
end
nil
end
private def pixel_to_point(bounds, pixel, upper_left, lower_right)
width = lower_right.real - upper_left.real
height = upper_left.imag - lower_right.imag
Complex.new(
real: upper_left.real + pixel[0] * width / bounds[0],
imag: upper_left.imag - pixel[1] * height / bounds[1]
)
end
private def render(pixels, bounds, upper_left, lower_right)
(0...bounds[1]).each do |row|
(0...bounds[0]).each do |col|
point = pixel_to_point(bounds, {col, row}, upper_left, lower_right)
count = escape_time(point, 255)
pixels[row * bounds[0] + col] = count ? 255 - count : 0
end
end
end
end
MandelbrotGenerator.new.generate
#!/usr/bin/env ruby
# Port of the Rust code for benchmarking purposes, does not mimick error
# handling because it wouldn't make a difference.
require "complex"
class MandelbrotGenerator
def generate
bounds = ARGV[1].split('x').map(&:to_i)
upper_left = parse_complex(ARGV[2])
lower_right = parse_complex(ARGV[3])
pixels = Array.new(bounds[0] * bounds[1], 0)
render(pixels, bounds, upper_left, lower_right)
end
private def parse_complex(str)
real, imag = str.split(',').map(&:to_f)
Complex(real, imag)
end
private def escape_time(c, limit)
# We could save one iteration, but let's do it like it is done in the book.
z = Complex(0.0, 0.0)
(0...limit).each do |i|
return i if z.abs2 > 4
z = z * z + c
end
nil
end
private def pixel_to_point(bounds, pixel, upper_left, lower_right)
width = lower_right.real - upper_left.real
height = upper_left.imag - lower_right.imag
Complex(
upper_left.real + pixel[0] * width / bounds[0],
upper_left.imag - pixel[1] * height / bounds[1]
)
end
private def render(pixels, bounds, upper_left, lower_right)
(0...bounds[1]).each do |row|
(0...bounds[0]).each do |col|
point = pixel_to_point(bounds, [col, row], upper_left, lower_right)
count = escape_time(point, 255)
pixels[row * bounds[0] + col] = count ? 255 - count : 0
end
end
end
end
MandelbrotGenerator.new.generate
% hyperfine './mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' 'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20'
Benchmark #1: ./mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20
Time (mean ± σ): 3.528 s ± 0.021 s [User: 3.516 s, System: 0.012 s]
Range (min … max): 3.500 s … 3.564 s 10 runs
Benchmark #2: target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20
Time (mean ± σ): 3.431 s ± 0.038 s [User: 3.423 s, System: 0.005 s]
Range (min … max): 3.383 s … 3.506 s 10 runs
Summary
'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' ran
1.03 ± 0.01 times faster than './mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20'
% hyperfine './mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20'
Benchmark #1: ./mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20
Time (mean ± σ): 3.639 s ± 0.074 s [User: 3.600 s, System: 0.013 s]
Range (min … max): 3.549 s … 3.818 s 10 runs
Benchmark #2: ./mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20
Time (mean ± σ): 398.077 s ± 2.257 s [User: 397.740 s, System: 0.209 s]
Range (min … max): 395.474 s … 403.707 s 10 runs
Summary
'./mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' ran
109.39 ± 2.30 times faster than './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20'
mandelbrot [main] % hyperfine 'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20'
Benchmark #1: target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20
Time (mean ± σ): 3.486 s ± 0.032 s [User: 3.479 s, System: 0.005 s]
Range (min … max): 3.435 s … 3.530 s 10 runs
Benchmark #2: ./mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20
Time (mean ± σ): 5.177 s ± 0.050 s [User: 7.003 s, System: 0.232 s]
Range (min … max): 5.118 s … 5.289 s 10 runs
Summary
'target/release/mandelbrot mandel.png 4000x3000 -1.20,0.35 -1,0.20' ran
1.49 ± 0.02 times faster than './mandelbrot.rb mandel.png 4000x3000 -1.20,0.35 -1,0.20'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment