Skip to content

Instantly share code, notes, and snippets.

@denilsonsa
Last active July 2, 2024 15:43
Show Gist options
  • Save denilsonsa/5076dab06158eb97e55c5fa72b454cc7 to your computer and use it in GitHub Desktop.
Save denilsonsa/5076dab06158eb97e55c5fa72b454cc7 to your computer and use it in GitHub Desktop.
Python Mandelbrot fractal

Python Mandelbrot fractal

Trivial implementation of the Mandelbrot fractal written in pure Python. It outputs to the terminal using 24-bit ANSI escape codes, which is widely supported by many terminals.

No optimization was done to the code. It's supposed to be simple and straightforward.

There is a one-to-one mapping between each calculated point from the fractal and one output character. This results in a low-resolution image made of characters (instead of pixels), but it is simple to implement, simple to understand, and widely portable/compatible.

This code is compatible with Python 3 and greatly benefits from running it inside PyPy.

#!/usr/bin/env python3
import argparse
import doctest
def float_range(start, end, steps):
"""Similar to numpy.linspace(), but written in pure Python.
Extreme cases:
>>> list(float_range(-5, 5, 0))
[]
>>> list(float_range(-5, 5, 1))
[-5.0]
More useful cases:
>>> list(float_range(-5, 5, 2))
[-5.0, 5.0]
>>> list(float_range(-5, 5, 3))
[-5.0, 0.0, 5.0]
>>> list(float_range(-5, 5, 5))
[-5.0, -2.5, 0.0, 2.5, 5.0]
>>> list(float_range(0, 2, 5))
[0.0, 0.5, 1.0, 1.5, 2.0]
>>> list(float_range(0, 3, 4))
[0.0, 1.0, 2.0, 3.0]
Also in reverse order:
>>> list(float_range(5, -5, 5))
[5.0, 2.5, 0.0, -2.5, -5.0]
"""
if steps <= 0:
return
if steps == 1:
yield start * 1.0
return
for i in range(steps):
yield start + (end - start) * (i / (steps - 1))
def mandelbrot_0(c, iters):
z = c
nv = 0
for i in range(iters):
if abs(z) > 2:
break
z = z * z + c
nv += 1
return nv
def calculate_mandelbrot(width, height, iters):
for y in float_range(-1, 1, height):
for x in float_range(-1.5, 0.5, width):
c = mandelbrot_0(complex(x, y), iters)
rgb = round(c / iters * (256 ** 3 - 1))
print('\033[48;2;{r};{g};{b}m '.format(
r=round((rgb >> 16) & 0xFF),
g=round((rgb >> 8) & 0xFF),
b=round((rgb >> 0) & 0xFF),
), end='')
print('\033[m')
def main():
parser = argparse.ArgumentParser(
prog='Mandelbrot in the terminal',
# Don't add `-h` and `--help`, because it conflicts with `--height`.
add_help=False,
)
parser.add_argument('--help', action='help', help='Show this help message')
parser.add_argument('-t', '--test', action='store_true', help='Run tests')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose test output')
parser.add_argument('-w', '--width', type=int, default=80, help='Width, in number of characters')
parser.add_argument('-h', '--height', type=int, default=25, help='Height, in number of lines')
parser.add_argument('-i', '--iterations', type=int, default=5000, help='Height, in number of lines')
args = parser.parse_args()
if args.test:
doctest.testmod(verbose=args.verbose)
else:
calculate_mandelbrot(args.width, args.height, args.iterations)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment