Title: Draw escape time polynomial fractals in Python
Earlier I made two posts that explain how to draw Mandelbrot set and Julia set fractals.
This example generalizes the approach used by those two sets to produce a new family of fractals.
Approach
Here's the basic approach used to draw the Mandelbrot and Julia sets.
- For each pixel location (x, y) in the image:
- Initialize Z0 to some value
- Iterate the equation Zn+1 = F(Zn) for some function F until either:
- The magnitude of Zn diverges toward infinity
- A certain number of iterations have passed
- Use the number of iterations that were performed to set the pixel's color.
To draw the Mandelbrot and Julia sets, you use specific values for Z0 and the function F. To get a Mandelbrot set:
- Z0 = 0
- F is Zn+12 + (x + y * j)
To get a Julia set:
- Z0 = (x + y * j)
- F is Zn+12 + 1
This example lets you enter your own values for those two parameters at run time.
Parsing Functions
Here's the code that parses the equations that you enter into the Entry widgets.
# Get the Z0 and Zn+1 functions.
function_def = \
f'''def func_z0(x, y):
try:
return {self.z0_var.get()}
except:
return 1000'''
exec(function_def, globals())
if SHOW_FUNCTIONS:
print(function_def, '\n')
function_def = \
f'''def func_zn_plus_1(zn, x, y):
try:
return {self.zn_plus_1_var.get()}
except:
return 1000'''
exec(function_def, globals())
if SHOW_FUNCTIONS:
print(function_def, '\n')
This code sets variable function_def to a string that defines a function that incorporates the text you entered. It then calls exec(function_def, globals()) to execute the string as Python code. The second parameter to exec stores the resulting function in the global namespace so the program's code can use it.
|
Note: In general, executing code entered by the user is not safe because the user could enter code that deletes files, executes other programs, or does other evil things to your computer.
Only use exec if you trust your users and know they won't enter anything naughty.
|
If the SHOW_FUNCTIONS variable is True, the program prints out the strings that define the functions. Here's one example.
def func_z0(x, y):
try:
return 0 + 0j
except:
return 1000
def func_zn_plus_1(zn, x, y):
try:
return zn ** 2 + x + y * 1j
except:
return 1000
These are just simple functions that return numeric results. The func_z0 function uses a pixel's coordinates (x, y) to set Z0 for the fractal algorithm. The function func_zn_plus_1 calculates Zn+1 from Zn and the pixel's coordinates.
Calling the Functions
The code that builds the fractal is similar to the previous Mandelbrot and Julia set code except it calls functions func_z0 and func_zn_plus_1 instead of using hard-wired functions. Here's that code with the new function calls highlighted in blue.
# Loop over the pixels.
for pix_x in range(image_wid):
for pix_y in range(image_hgt):
# Calculate this point's color.
x, y = self.d_to_w(pix_x, pix_y)
z0 = func_z0(x, y) # analysis:ignore
z = z0
iteration = 0
while ((iteration < max_iterations) and (abs(z) < escape_radius)):
# Calculate Z(clr).
z = func_zn_plus_1(z, x, y) # analysis:ignore
iteration += 1
# Set the pixel's color.
if (iteration >= max_iterations):
color = black
else:
if self.use_smooth_colors.get():
# Reduce the error in mu.
for i in range(3):
z = func_zn_plus_1(z, x, y) # analysis:ignore
iteration += 1
try:
# cmath.log provides a more interesting picture
# because it is defined for negative numbers
# while math.log is not.
mu = abs(iteration + 1 -
cmath.log(cmath.log(abs(z))) / log_escape)
except:
mu = 0
if not self.repeat_colors.get():
mu = mu / max_iterations * len(colors)
color = self.get_color(colors, mu)
else:
color = colors[iteration % len(colors)]
self.pixels[pix_x, pix_y] = color
Notice that the calls to the new functions are followed by the comment # analysis:ignore. Because the functions are not defined within the code, Spyder (or whatever Python environment you're using) can't see them so it won't understand what they are and it will display a warning. The comment tells Spyder to ignore this issue and not display the warning.
Meanwhile, the Python interpreter treats this is a normal comment and ignores it. Note that this is not a standard Python language thing, so some environments may not know how to use it.
the console
Conclusion
This version of the program lets you enter equations to set Z0 and determine how to calculate Zn+1. The program's Presets sub-menus contain several examples that you can use to generate the pictures below. You can also use them as starting points for your own experiments.
Be sure to enter the two equations correctly. In particular, you cannot say something like yj or y * j, you must use y * 1j instead. If you have any mistakes in the equations, the program will print a syntax error in the console and the user interface won't do anything.
Download the example to experiment with it and to see additional details. If you generate any interesting pictures, post them (or links to them) in the comments. Be sure to include your equations so others can reproduce your results.
|