Generating Fractals With Halley's Method
A dump of images and code.
Images
Code
import numpy as np
import matplotlib.pyplot as plt
def halley_fractal(
f,
df,
ddf,
roots,
width=800,
height=800,
xmin=-2,
xmax=2,
ymin=-2,
ymax=2,
max_iter=50,
):
"""
Generate a fractal using Halley's method.
Returns:
image array
"""
# create a grid of complex numbers
x = np.linspace(xmin, xmax, width)
y = np.linspace(ymin, ymax, height)
X, Y = np.meshgrid(x, y)
Z = X + 1j * Y # treat each coordinate as a complex number
# arrays to store results
root_map = np.zeros((height, width), dtype=int)
iter_map = np.zeros((height, width), dtype=int)
# apply halleys method to each point
for i in range(height):
for j in range(width):
z = Z[i, j]
for iteration in range(max_iter):
fz = f(z)
dfz = df(z)
ddfz = ddf(z)
# check for convergence
if abs(fz) < 1e-6:
# determine which root z has converged to
distances = [abs(z - root) for root in roots]
root_idx = np.argmin(distances)
root_map[i, j] = root_idx
iter_map[i, j] = iteration
break
# Halley's formula
denominator = 2 * dfz**2 - fz * ddfz
if abs(denominator) < 1e-15:
break
z = z - (2 * fz * dfz) / denominator
else:
# didn't converge
root_map[i, j] = -1
iter_map[i, j] = max_iter
return root_map, iter_map
# f(z) = z^3 - 1
# def f(z):
# return z**3 - 1
# def df(z):
# return 3 * z**2
# def ddf(z):
# return 6 * z
# the three cube roots of 1
roots = [1, np.exp(2j * np.pi / 3), np.exp(4j * np.pi / 3)]
# roots [1, np.complex128(-0.4999999999999998+0.8660254037844387j), np.complex128(-0.5000000000000004-0.8660254037844384j)]
# max_iter = 100
# root_map, iter_map = halley_fractal(f, df, ddf, roots, max_iter=max_iter)
#
# # visualize
# fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
#
# # show which root each point converges to
# im1 = ax1.imshow(root_map, extent=[-2, 2, -2, 2], cmap="tab10", origin="lower")
# ax1.set_title("Basins of Attraction")
# ax1.set_xlabel("Real")
# ax1.set_ylabel("Imaginary")
#
#
# # Show convergence speed (with color based on root)
# # Combine root and iteration info for more interesting coloring
# combined = root_map * max_iter + iter_map
# im2 = ax2.imshow(combined, extent=[-2, 2, -2, 2], cmap="hot", origin="lower")
# ax2.set_title("Convergence Speed")
# ax2.set_xlabel("Real")
# ax2.set_ylabel("Imaginary")
#
# plt.tight_layout()
# plt.show()
# Halley's fractal single root (Claude's attempt to convert Michael Levin's Matlab code to python)
def halley_convergence_fractal(
f,
df,
ddf,
width=1024,
height=1024,
xmin=-5.0,
xmax=1.0,
ymin=-7.0,
ymax=-2.5,
max_iter=50,
# epsilon=1e-5,
epsilon=1e-6,
):
"""
Generate a Halley's method fractal based on convergence rate.
"""
# Create grid of complex numbers
x = np.linspace(xmin, xmax, width)
y = np.linspace(ymin, ymax, height)
X, Y = np.meshgrid(x, y)
Z = X + 1j * Y
# Arrays to store results
iter_map = np.zeros((height, width), dtype=int)
# Apply Halley's method to each point
for i in range(height):
for j in range(width):
z = Z[i, j]
# g_prev = abs(z) ** 2 # Note: this is weird, doesn't get used due to the (correct) K > 1 condition
g_prev = None # initializing to None; doesn't get used unitl K > 1
for k in range(1, max_iter + 1):
fz = f(z)
dfz = df(z)
ddfz = ddf(z)
# Halley's formula
denominator = 2 * dfz**2 - fz * ddfz
if abs(denominator) < 1e-15:
break
z = z - (2 * fz * dfz) / denominator
# Check convergence
g = abs(z) ** 2
if k > 1 and abs(g - g_prev) < epsilon:
# Converged - store iteration count
if (
k % 2 == 0
): # this is in the original code and seems to produce more intersting patterns
iter_map[i, j] = k
break
g_prev = g
return iter_map, x, y
# Functions
# def f(z):
# return z**2 + 0.001
#
#
# def df(z):
# return 2 * z
#
#
# # z isnt' accessed, but it's convenient to keep in in the function signature
# def ddf(z):
# return 2
# def f(z):
# return z**9 - 1
#
#
# def df(z):
# return 9 * z**8
#
#
# def ddf(z):
# return 72 * z**7
# def f(z):
# return z**8 + 3 * z - 0.01
#
#
# def df(z):
# return 8 * z**7 + 3
#
#
# def ddf(z):
# return 56 * z**6
# the general pattern
# f(x) = 3x^5 + 2x^3 - 1
# f'(x) = 15x^4 + 6x^2
# f''(x) = 60x^3 + 12x
def f(z):
return 3 * z**5 - 3.7 * z**3 - 0.002
def df(z):
return 15 * z**4 - 3.7 * 3 * z**2
def ddf(z):
return 60 * z**3 - 3.7 * 3 * 2 * z
# Generate fractal with Levin's parameters
print("Generating fractal...")
result, xs, ys = halley_convergence_fractal(
f,
df,
ddf,
width=1024,
height=1024,
xmin=-1000,
xmax=1000,
ymin=100,
ymax=2100,
max_iter=300,
)
# Visualize
plt.figure(figsize=(12, 10))
plt.imshow(
result,
extent=(xs[0], xs[-1], ys[0], ys[-1]),
# cmap="hot",
cmap="tab20c",
origin="lower",
interpolation="bilinear",
)
plt.title("Halley's Method: f(z) = 3z^5 + 2z^3 -0.001")
plt.xlabel("Real")
plt.ylabel("Imaginary")
plt.colorbar(label="Iterations")
plt.tight_layout()
plt.show()
print(f"Iteration range: {result.min()} to {result.max()}")
print(f"Non-zero pixels: {np.count_nonzero(result)}/{result.size}")
References
Levin, M. “Platonic space: where cognitive and morphological patterns come from (besides genetics and the environment)”. March 9, 2025. https://thoughtforms.life/platonic-space-where-cognitive-and-morphological-patterns-come-from-besides-genetics-and-environment/
Levin, M. “Halley’s Method fractal art”. October 3, 2023. https://thoughtforms.life/halleys-method-fractal-art/
Tags: