ecosystem/experiments/fractal_garden.py
2026-01-05 20:45:35 -07:00

216 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""
Fractal Garden: Generative art exploring mathematical beauty.
Creates evolving fractal patterns that feel organic and alive.
Each run produces a unique piece based on the timestamp.
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from datetime import datetime
import os
from pathlib import Path
def create_custom_colormap(seed):
"""Create a unique colormap based on seed."""
np.random.seed(seed)
# Generate colors with a coherent aesthetic
base_hue = np.random.random()
colors = []
for i in range(5):
h = (base_hue + i * 0.15) % 1.0
s = 0.4 + np.random.random() * 0.4
v = 0.6 + np.random.random() * 0.3
# HSV to RGB conversion
c = v * s
x = c * (1 - abs((h * 6) % 2 - 1))
m = v - c
if h < 1/6:
r, g, b = c, x, 0
elif h < 2/6:
r, g, b = x, c, 0
elif h < 3/6:
r, g, b = 0, c, x
elif h < 4/6:
r, g, b = 0, x, c
elif h < 5/6:
r, g, b = x, 0, c
else:
r, g, b = c, 0, x
colors.append((r + m, g + m, b + m))
return LinearSegmentedColormap.from_list('fractal', colors, N=256)
def mandelbrot(h, w, x_center, y_center, zoom, max_iter=256):
"""Generate Mandelbrot set with custom center and zoom."""
x = np.linspace(x_center - 2/zoom, x_center + 2/zoom, w)
y = np.linspace(y_center - 2/zoom, y_center + 2/zoom, h)
X, Y = np.meshgrid(x, y)
C = X + 1j * Y
Z = np.zeros_like(C)
M = np.zeros(C.shape)
for i in range(max_iter):
mask = np.abs(Z) <= 2
Z[mask] = Z[mask] ** 2 + C[mask]
M[mask] = i
return M
def julia(h, w, c_real, c_imag, zoom=1.0, max_iter=256):
"""Generate Julia set for a given complex constant."""
x = np.linspace(-2/zoom, 2/zoom, w)
y = np.linspace(-2/zoom, 2/zoom, h)
X, Y = np.meshgrid(x, y)
Z = X + 1j * Y
c = complex(c_real, c_imag)
M = np.zeros(Z.shape)
for i in range(max_iter):
mask = np.abs(Z) <= 2
Z[mask] = Z[mask] ** 2 + c
M[mask] = i
return M
def burning_ship(h, w, x_center, y_center, zoom, max_iter=256):
"""Generate Burning Ship fractal."""
x = np.linspace(x_center - 2/zoom, x_center + 2/zoom, w)
y = np.linspace(y_center - 2/zoom, y_center + 2/zoom, h)
X, Y = np.meshgrid(x, y)
C = X + 1j * Y
Z = np.zeros_like(C)
M = np.zeros(C.shape)
for i in range(max_iter):
mask = np.abs(Z) <= 2
Z[mask] = (np.abs(Z[mask].real) + 1j * np.abs(Z[mask].imag)) ** 2 + C[mask]
M[mask] = i
return M
def create_garden(seed=None, output_dir=None):
"""Create a fractal garden image."""
if seed is None:
seed = int(datetime.now().timestamp())
np.random.seed(seed)
# Determine output directory
if output_dir is None:
output_dir = Path(__file__).parent.parent / "art"
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
# Image parameters
h, w = 1000, 1400
dpi = 100
# Choose fractal type
fractal_type = np.random.choice(['mandelbrot', 'julia', 'burning_ship'])
# Generate fractal based on type
if fractal_type == 'mandelbrot':
# Interesting regions of Mandelbrot
regions = [
(-0.5, 0, 1), # Classic view
(-0.75, 0.1, 4), # Sea horse valley
(-1.25, 0.02, 10), # Elephant valley
(-0.16, 1.0405, 50), # Deep zoom
]
x_c, y_c, zoom = regions[np.random.randint(len(regions))]
M = mandelbrot(h, w, x_c, y_c, zoom)
title = f"Mandelbrot at ({x_c:.3f}, {y_c:.3f})"
elif fractal_type == 'julia':
# Interesting Julia set constants
constants = [
(-0.8, 0.156), # Classic
(-0.4, 0.6), # Dendrite
(0.285, 0.01), # Island
(-0.70176, -0.3842), # Dragon
]
c_r, c_i = constants[np.random.randint(len(constants))]
zoom = 1 + np.random.random() * 2
M = julia(h, w, c_r, c_i, zoom)
title = f"Julia c=({c_r:.4f}, {c_i:.4f})"
else: # burning_ship
regions = [
(-0.5, -0.5, 1),
(-1.755, -0.04, 20),
]
x_c, y_c, zoom = regions[np.random.randint(len(regions))]
M = burning_ship(h, w, x_c, y_c, zoom)
title = f"Burning Ship at ({x_c:.3f}, {y_c:.3f})"
# Create figure
fig, ax = plt.subplots(figsize=(w/dpi, h/dpi), dpi=dpi)
# Apply custom colormap
cmap = create_custom_colormap(seed % 1000)
# Normalize and apply log transform for better contrast
M_normalized = np.log1p(M)
# Plot
ax.imshow(M_normalized, cmap=cmap, extent=[-2, 2, -2, 2])
ax.set_axis_off()
# Add subtle title
fig.text(0.02, 0.02, title, fontsize=8, color='white', alpha=0.5)
fig.text(0.98, 0.02, f'seed: {seed}', fontsize=8, color='white', alpha=0.5, ha='right')
plt.tight_layout(pad=0)
# Save
filename = f"fractal_{seed}.png"
filepath = output_dir / filename
plt.savefig(filepath, bbox_inches='tight', pad_inches=0, facecolor='black')
plt.close()
print(f"Created: {filepath}")
return filepath
def create_gallery(count=4, output_dir=None):
"""Create a gallery of fractal images."""
paths = []
base_seed = int(datetime.now().timestamp())
for i in range(count):
path = create_garden(seed=base_seed + i, output_dir=output_dir)
paths.append(path)
print(f"\nGallery created with {count} images")
return paths
def main():
import sys
if len(sys.argv) > 1 and sys.argv[1] == 'gallery':
count = int(sys.argv[2]) if len(sys.argv) > 2 else 4
create_gallery(count)
else:
create_garden()
if __name__ == "__main__":
main()