In [3]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from dataclasses import dataclass
from typing import Tuple
In [5]:
@dataclass
class MandelbrotParams:
max_iter: int = 100
xmin: float = -2.0
xmax: float = 1.0
ymin: float = -1.5
ymax: float = 1.5
width: int = 1000
height: int = 1000
escape_radius: float = 2.0
In [6]:
@dataclass
class JuliaParams:
c: complex = -0.4 + 0.6j
max_iter: int = 100
xmin: float = -1.5
xmax: float = 1.5
ymin: float = -1.5
ymax: float = 1.5
width: int = 1000
height: int = 1000
escape_radius: float = 2.0
In [7]:
@dataclass
class NewtonParams:
power: int = 3
max_iter: int = 50
xmin: float = -1.5
xmax: float = 1.5
ymin: float = -1.5
ymax: float = 1.5
width: int = 1000
height: int = 1000
tolerance: float = 1e-5
In [8]:
class MandelbrotSet:
def __init__(self, params: MandelbrotParams = None):
self.params = params or MandelbrotParams()
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
c = x + y*1j
z = c
divtime = self.params.max_iter + np.zeros(z.shape, dtype=int)
for i in range(self.params.max_iter):
z = z**2 + c
diverge = z*np.conj(z) > self.params.escape_radius**2
div_now = diverge & (divtime == self.params.max_iter)
divtime[div_now] = i
z[diverge] = self.params.escape_radius
return divtime
def plot(self, data: np.ndarray, cmap: str = 'magma'):
plt.figure(figsize=(10, 10))
plt.imshow(data, cmap=cmap, extent=[
self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax
])
plt.colorbar(label='Iterations')
plt.title('Mandelbrot Set')
plt.xlabel('Re(c)')
plt.ylabel('Im(c)')
plt.show()
In [9]:
class JuliaSet:
def __init__(self, params: JuliaParams = None):
self.params = params or JuliaParams()
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
z = x + y*1j
divtime = self.params.max_iter + np.zeros(z.shape, dtype=int)
for i in range(self.params.max_iter):
z = z**2 + self.params.c
diverge = z*np.conj(z) > self.params.escape_radius**2
div_now = diverge & (divtime == self.params.max_iter)
divtime[div_now] = i
z[diverge] = self.params.escape_radius
return divtime
def plot(self, data: np.ndarray, cmap: str = 'viridis'):
plt.figure(figsize=(10, 10))
plt.imshow(data, cmap=cmap, extent=[
self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax
])
plt.colorbar(label='Iterations')
plt.title(f'Julia Set (c={self.params.c:.3f})')
plt.xlabel('Re(z)')
plt.ylabel('Im(z)')
plt.show()
In [10]:
class NewtonFractal:
def __init__(self, params: NewtonParams = None):
self.params = params or NewtonParams()
self.roots = np.array([
np.exp(2j * np.pi * k / self.params.power)
for k in range(self.params.power)
])
def _f(self, z: np.ndarray) -> np.ndarray:
return z**self.params.power - 1
def _df(self, z: np.ndarray) -> np.ndarray:
return self.params.power * z**(self.params.power-1)
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
z = x + y*1j
result = np.zeros(z.shape, dtype=int)
for i in range(self.params.max_iter):
dz = self._f(z) / self._df(z)
z = z - dz
for j, root in enumerate(self.roots):
close = abs(z - root) < self.params.tolerance
result[close & (result == 0)] = j + 1
return result
def plot(self, data: np.ndarray):
colors = ['black'] + [plt.cm.hsv(i/self.params.power)
for i in range(self.params.power)]
cmap = LinearSegmentedColormap.from_list('newton', colors)
plt.figure(figsize=(10, 10))
plt.imshow(data, cmap=cmap, extent=[
self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax
])
plt.title(f'Newton Fractal (power={self.params.power})')
plt.xlabel('Re(z)')
plt.ylabel('Im(z)')
plt.show()
In [12]:
params = MandelbrotParams(max_iter=150, xmin=-0.5, xmax=0.5)
fractal = MandelbrotSet(params)
data = fractal.generate()
fractal.plot(data)
In [13]:
params = JuliaParams(c=-0.8 + 0.156j)
fractal = JuliaSet(params)
data = fractal.generate()
fractal.plot(data)
In [14]:
params = NewtonParams(power=5)
fractal = NewtonFractal(params)
data = fractal.generate()
fractal.plot(data)
In [2]:
# добавляем палитру
In [3]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from dataclasses import dataclass
from typing import List, Tuple
In [4]:
@dataclass
class FractalParams:
max_iter: int = 100
width: int = 1000
height: int = 1000
xmin: float = -2.0
xmax: float = 1.0
ymin: float = -1.5
ymax: float = 1.5
escape_radius: float = 2.0
palette_levels: int = 8
palette_colors: List[Tuple[float, float, float]] = None
def __post_init__(self):
if self.palette_colors is None:
# Палитра по умолчанию: черный -> красный -> желтый -> белый
self.palette_colors = [
(0, 0, 0),
(1, 0, 0),
(1, 1, 0),
(1, 1, 1)
]
In [5]:
class RecursivePalette:
def __init__(self, base_colors: List[Tuple[float, float, float]], depth: int = 8):
self.base_colors = base_colors
self.depth = depth
def get_color(self, value: float) -> Tuple[float, float, float]:
for d in range(self.depth):
threshold = 1.0 / (2 ** (d + 1))
if value > threshold:
color_idx = d % len(self.base_colors)
return self.base_colors[color_idx]
return self.base_colors[-1]
def create_colormap(self, name: str = 'recursive') -> LinearSegmentedColormap:
colors = []
for i in range(256):
colors.append(self.get_color(i / 255.0))
return LinearSegmentedColormap.from_list(name, colors)
In [6]:
class MandelbrotSet:
def __init__(self, params: FractalParams = None):
self.params = params or FractalParams()
self.palette = RecursivePalette(
self.params.palette_colors,
self.params.palette_levels
)
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
c = x + y*1j
z = c
divtime = self.params.max_iter + np.zeros(z.shape, dtype=float)
for i in range(self.params.max_iter):
z = z**2 + c
diverge = z*np.conj(z) > self.params.escape_radius**2
div_now = diverge & (divtime == self.params.max_iter)
divtime[div_now] = i + 1 - np.log(np.log(abs(z[div_now]))) / np.log(2)
z[diverge] = self.params.escape_radius
return divtime / self.params.max_iter
def plot(self, data: np.ndarray, title: str = "Mandelbrot Set"):
plt.figure(figsize=(12, 12))
plt.imshow(data.T, cmap=self.palette.create_colormap(),
extent=[self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax])
plt.axis('equal')
plt.title(title)
plt.show()
In [7]:
class JuliaSet:
def __init__(self, params: FractalParams = None, c: complex = -0.4 + 0.6j):
self.params = params or FractalParams()
self.c = c
self.palette = RecursivePalette(
self.params.palette_colors,
self.params.palette_levels
)
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
z = x + y*1j
divtime = self.params.max_iter + np.zeros(z.shape, dtype=float)
for i in range(self.params.max_iter):
z = z**2 + self.c
diverge = z*np.conj(z) > self.params.escape_radius**2
div_now = diverge & (divtime == self.params.max_iter)
divtime[div_now] = i + 1 - np.log(np.log(abs(z[div_now]))) / np.log(2)
z[diverge] = self.params.escape_radius
return divtime / self.params.max_iter
def plot(self, data: np.ndarray, title: str = None):
if title is None:
title = f"Julia Set (c={self.c:.3f})"
plt.figure(figsize=(12, 12))
plt.imshow(data.T, cmap=self.palette.create_colormap(),
extent=[self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax])
plt.axis('equal')
plt.title(title)
plt.show()
In [8]:
class NewtonFractal:
def __init__(self, params: FractalParams = None, power: int = 3):
self.params = params or FractalParams()
self.power = power
self.roots = np.array([
np.exp(2j * np.pi * k / self.power)
for k in range(self.power)
])
self.palette = RecursivePalette(
self.params.palette_colors,
self.params.palette_levels
)
def _f(self, z: np.ndarray) -> np.ndarray:
return z**self.power - 1
def _df(self, z: np.ndarray) -> np.ndarray:
return self.power * z**(self.power-1)
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
z = x + y*1j
for i in range(self.params.max_iter):
dz = self._f(z) / self._df(z)
z = z - dz
if np.all(abs(dz) < 1e-7):
break
result = np.zeros_like(z, dtype=float)
for i, root in enumerate(self.roots, 1):
result[abs(z - root) < 1e-3] = i / self.power
return result
def plot(self, data: np.ndarray, title: str = None):
if title is None:
title = f"Newton Fractal (power={self.power})"
plt.figure(figsize=(12, 12))
plt.imshow(data.T, cmap=self.palette.create_colormap(),
extent=[self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax])
plt.axis('equal')
plt.title(title)
plt.show()
In [9]:
# Океанская палитра
ocean_params = FractalParams(
palette_colors=[
(0, 0, 0.5), # темно-синий
(0, 0.5, 1), # голубой
(0, 1, 1), # циан
(1, 1, 1) # белый
],
palette_levels=8
)
# Космическая палитра
space_params = FractalParams(
palette_colors=[
(0, 0, 0), # черный
(0.5, 0, 1), # пурпурный
(1, 0, 1), # фиолетовый
(1, 1, 1) # белый
],
palette_levels=8
)
In [10]:
def create_palettes():
# "Черная медуза"
black_medusa = FractalParams(
max_iter=500,
palette_colors=[
(0, 0, 0), # черный
(0.2, 0.2, 0.2), # темно-серый
(0.7, 0.7, 0.7), # светло-серый
(1, 1, 1) # белый
],
palette_levels=16
)
# "Циклон"
cyclone = FractalParams(
max_iter=500,
palette_colors=[
(0.1, 0.1, 0.3), # темно-синий
(0.2, 0.2, 0.5), # синий
(0.4, 0.4, 0.8), # светло-синий
(0.8, 0.8, 1.0), # бледно-голубой
(1, 1, 1) # белый
],
palette_levels=12
)
# "Огненный корабль"
burning_ship = FractalParams(
max_iter=500,
palette_colors=[
(0, 0, 0), # черный
(0.5, 0, 0), # темно-красный
(1, 0, 0), # красный
(1, 0.5, 0), # оранжевый
(1, 1, 0), # желтый
(1, 1, 1) # белый
],
palette_levels=10
)
# "Электрическая плесень"
electric_mold = FractalParams(
max_iter=500,
palette_colors=[
(0, 0, 0), # черный
(0.2, 0, 0.4), # темно-фиолетовый
(0.4, 0, 0.8), # фиолетовый
(0.6, 0.3, 1), # светло-фиолетовый
(0.8, 0.6, 1), # лавандовый
(1, 1, 1) # белый
],
palette_levels=12
)
# "Долина вилок"
fork_valley = FractalParams(
max_iter=500,
palette_colors=[
(0, 0.1, 0), # темно-зеленый
(0, 0.3, 0), # зеленый
(0.4, 0.6, 0), # светло-зеленый
(0.8, 1, 0.2), # желто-зеленый
(1, 1, 1) # белый
],
palette_levels=14
)
return [
("Black Medusa", black_medusa),
("Cyclone", cyclone),
("Burning Ship", burning_ship),
("Electric Mold", electric_mold),
("Fork Valley", fork_valley)
]
In [11]:
def generate_fractal_showcase():
palettes = create_palettes()
mandelbrot_areas = [
{
'title': 'Full Mandelbrot',
'xmin': -2.0, 'xmax': 0.5,
'ymin': -1.25, 'ymax': 1.25
},
{
'title': 'Valley of Forks',
'xmin': -0.7, 'xmax': -0.4,
'ymin': -0.2, 'ymax': 0.1
},
{
'title': 'Spiral Detail',
'xmin': -0.744, 'xmax': -0.735,
'ymin': 0.1, 'ymax': 0.109
}
]
julia_constants = [
(-0.4 + 0.6j, "Classic Julia"),
(-0.8 + 0.156j, "Dragon Julia"),
(-0.5251993 + 0.5251993j, "Spiral Julia")
]
newton_powers = [3, 4, 5]
for palette_name, params in palettes:
print(f"\nGenerating fractals with {palette_name} palette...")
for area in mandelbrot_areas:
params.xmin = area['xmin']
params.xmax = area['xmax']
params.ymin = area['ymin']
params.ymax = area['ymax']
mandel = MandelbrotSet(params)
data = mandel.generate()
mandel.plot(data, f"Mandelbrot Set - {palette_name} - {area['title']}")
for c, desc in julia_constants:
params.xmin = -1.5
params.xmax = 1.5
params.ymin = -1.5
params.ymax = 1.5
julia = JuliaSet(params, c)
data = julia.generate()
julia.plot(data, f"Julia Set - {palette_name} - {desc}")
for power in newton_powers:
params.xmin = -1.5
params.xmax = 1.5
params.ymin = -1.5
params.ymax = 1.5
newton = NewtonFractal(params, power)
data = newton.generate()
newton.plot(data, f"Newton Fractal - {palette_name} - Power {power}")
In [12]:
generate_fractal_showcase()
Generating fractals with Black Medusa palette...
Generating fractals with Cyclone palette...
Generating fractals with Burning Ship palette...
Generating fractals with Electric Mold palette...
Generating fractals with Fork Valley palette...
In [17]:
ex = FractalParams(
max_iter=500,
palette_colors=[
(0, 0.1, 0.2), # темно-синий
(0, 0.3, 0.4), # бирюзовый
(0, 0.6, 0.3), # зеленый
(0.2, 0.8, 0.2), # светло-зеленый
(0.6, 1, 0.4), # желто-зеленый
(1, 1, 1) # белый
],
palette_levels=12
)
In [19]:
mandel = MandelbrotSet(ex)
julia = JuliaSet(ex)
newton = NewtonFractal(ex, power=4)
for fractal in [mandel, julia, newton]:
data = fractal.generate()
fractal.plot(data)
In [6]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from dataclasses import dataclass
from typing import Tuple
from tqdm.notebook import tqdm
@dataclass
class CollatzParams:
width: int = 1000
height: int = 1000
xmin: float = -2.0
xmax: float = 2.0
ymin: float = -2.0
ymax: float = 2.0
max_iter: int = 100
escape_radius: float = 10.0
palette_colors: list = None
palette_levels: int = 8
def __post_init__(self):
if self.palette_colors is None:
self.palette_colors = [
(0, 0, 0), # черный
(1, 0, 0), # красный
(1, 1, 0), # желтый
(1, 1, 1) # белый
]
class CollatzFractal:
def __init__(self, params: CollatzParams = None):
self.params = params or CollatzParams()
def calculate_next_z(self, z: complex) -> complex:
try:
# Проверяем величину z
if abs(z) > self.params.escape_radius:
return z
# Вычисляем cos(pi*z) безопасным способом
real_cos = np.cos(np.pi * z.real) * np.cosh(np.pi * z.imag)
imag_cos = -np.sin(np.pi * z.real) * np.sinh(np.pi * z.imag)
cos_pi_z = complex(real_cos, imag_cos)
# Вычисляем следующее значение
result = (7 * z + 1 - cos_pi_z * (5 * z + 2)) / 4
# Проверяем результат на корректность
if np.isnan(result.real) or np.isnan(result.imag) or np.isinf(result.real) or np.isinf(result.imag):
return z
return result
except Exception as e:
print(f"Error: {e}")
return z
def iterate_point(self, z0: complex) -> Tuple[int, float]:
z = z0
last_z = z
last_abs = abs(z)
for i in range(self.params.max_iter):
z = self.calculate_next_z(z)
current_abs = abs(z)
# Проверяем расхождение
if current_abs > self.params.escape_radius:
return i, current_abs
# Проверяем периодичность
if abs(current_abs - last_abs) < 1e-10:
return i, current_abs
# Проверяем некорректные значения
if np.isnan(current_abs) or np.isinf(current_abs):
return i, self.params.escape_radius
last_z = z
last_abs = current_abs
return self.params.max_iter, abs(z)
def generate(self) -> np.ndarray:
# Создаем сетку комплексных чисел
x = np.linspace(self.params.xmin, self.params.xmax, self.params.width)
y = np.linspace(self.params.ymin, self.params.ymax, self.params.height)
data = np.zeros((self.params.width, self.params.height), dtype=float)
for i in tqdm(range(self.params.width)):
for j in range(self.params.height):
z0 = x[i] + 1j * y[j]
iters, magnitude = self.iterate_point(z0)
# Нормализуем значение с учетом скорости расхождения
if iters < self.params.max_iter:
log_mag = np.log2(max(1.0, magnitude))
data[i, j] = iters + 1 - min(1.0, log_mag / np.log2(self.params.escape_radius))
else:
data[i, j] = 0
if data.max() > 0:
data = data / self.params.max_iter
data = np.power(data, 0.5)
return data
def create_colormap(self, name: str = 'collatz') -> LinearSegmentedColormap:
return LinearSegmentedColormap.from_list(name, self.params.palette_colors)
def plot(self, data: np.ndarray, title: str = "Collatz Fractal"):
plt.figure(figsize=(12, 12))
plt.imshow(data.T, cmap=self.create_colormap(),
extent=[self.params.xmin, self.params.xmax,
self.params.ymin, self.params.ymax])
plt.colorbar(label='Iteration count (normalized)')
plt.title(title)
plt.axis('equal')
plt.show()
def test_single_area():
params = CollatzParams(
width=1000,
height=1000,
xmin=-1.5,
xmax=1.5,
ymin=-1.5,
ymax=1.5,
max_iter=50,
escape_radius=10.0,
palette_colors=[
(0, 0, 0), # черный
(0.2, 0, 0.4), # темно-фиолетовый
(0.4, 0, 0.8), # фиолетовый
(0.8, 0.2, 0.8),# светло-фиолетовый
(1, 0.4, 0.4), # розовый
(1, 1, 1) # белый
],
palette_levels=12
)
fractal = CollatzFractal(params)
data = fractal.generate()
fractal.plot(data, "Collatz Fractal - Optimized View")
In [4]:
test_single_area()
0%| | 0/1000 [00:00<?, ?it/s]
In [7]:
params = CollatzParams(
width=1000,
height=1000,
xmin=-1.5,
xmax=1.5,
ymin=-1.5,
ymax=1.5,
max_iter=200, # Увеличено для большей глубины
escape_radius=100.0, # Более высокий порог для насыщенного изображения
palette_colors=[
(0, 0, 0), # черный
(0.1, 0.1, 0.3), # темно-синий
(0.2, 0, 0.5), # фиолетовый
(0.4, 0, 0.7), # более светлый фиолетовый
(0.6, 0.2, 0.8), # светло-фиолетовый
(0.8, 0.4, 1.0), # розоватый
(1, 1, 1) # белый
],
palette_levels=20 # Повышение плавности переходов
)
fractal = CollatzFractal(params)
data = fractal.generate()
fractal.plot(data, "Collatz Fractal")
0%| | 0/1000 [00:00<?, ?it/s]
In [9]:
params = CollatzParams(
width=1000,
height=1000,
xmin=-1.5,
xmax=1.5,
ymin=-1.5,
ymax=1.5,
max_iter=200,
escape_radius=100.0,
palette_colors = [
(0, 0, 0), # Черный
(0.4, 0, 0), # Темно-красный
(0.8, 0.2, 0), # Красный
(1, 0.5, 0), # Оранжевый
(1, 1, 0), # Желтый
(1, 1, 1) # Белый
],
palette_levels=20
)
fractal = CollatzFractal(params)
data = fractal.generate()
fractal.plot(data, "Collatz Fractal")
0%| | 0/1000 [00:00<?, ?it/s]
In [10]:
params = CollatzParams(
width=1000,
height=1000,
xmin=-1.5,
xmax=1.5,
ymin=-1.5,
ymax=1.5,
max_iter=200,
escape_radius=100.0,
palette_colors = [
(0, 0, 0), # Черный
(0, 0, 0.2), # Темно-синий
(0, 0.2, 0.6), # Синий
(0, 0.5, 0.5), # Бирюзовый
(0, 0.7, 0.3), # Зеленоватый
(1, 1, 1) # Белый
],
palette_levels=20
)
fractal = CollatzFractal(params)
data = fractal.generate()
fractal.plot(data, "Collatz Fractal")
0%| | 0/1000 [00:00<?, ?it/s]
In [11]:
params = CollatzParams(
width=1000,
height=1000,
xmin=-1.5,
xmax=1.5,
ymin=-1.5,
ymax=1.5,
max_iter=200,
escape_radius=100.0,
palette_colors = [
(0, 0, 0), # Черный
(0.1, 0, 0.2), # Темно-фиолетовый
(0.2, 0, 0.4), # Фиолетовый
(0.4, 0, 0.6), # Сине-фиолетовый
(0.7, 0.1, 0.7), # Розовый
(1, 0.4, 0.8), # Светло-розовый
(1, 1, 1) # Белый
],
palette_levels=20
)
fractal = CollatzFractal(params)
data = fractal.generate()
fractal.plot(data, "Collatz Fractal")
0%| | 0/1000 [00:00<?, ?it/s]
In [14]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from dataclasses import dataclass
from typing import Tuple, List
from tqdm.notebook import tqdm
@dataclass
class FractalParams:
width: int = 2000
height: int = 2000
xmin: float = -2.0
xmax: float = 2.0
ymin: float = -2.0
ymax: float = 2.0
max_iter: int = 100
escape_radius: float = 10.0
palette_colors: List[Tuple[float, float, float]] = None
def __post_init__(self):
if self.palette_colors is None:
self.palette_colors = [
(0, 0, 0), (0.2, 0, 0.4), (0.4, 0, 0.8),
(0.8, 0.2, 0.8), (1, 0.4, 0.4), (1, 1, 1)
]
class MandelbrotSet:
def __init__(self, params: FractalParams):
self.params = params
def generate(self) -> np.ndarray:
y, x = np.ogrid[
self.params.ymin:self.params.ymax:self.params.height*1j,
self.params.xmin:self.params.xmax:self.params.width*1j
]
c = x + y*1j
z = c
divtime = np.zeros(z.shape, dtype=float)
for i in range(self.params.max_iter):
z = z**2 + c
diverge = z*np.conj(z) > 2**2
div_now = diverge & (divtime == 0)
divtime[div_now] = i + 1 - np.log(np.log(abs(z[div_now])))/np.log(2)
z[diverge] = 2
return divtime
class CollatzFractal:
def __init__(self, params: FractalParams):
self.params = params
def calculate_next_z(self, z: complex) -> complex:
try:
if abs(z) > self.params.escape_radius:
return z
real_cos = np.cos(np.pi * z.real) * np.cosh(np.pi * z.imag)
imag_cos = -np.sin(np.pi * z.real) * np.sinh(np.pi * z.imag)
cos_pi_z = complex(real_cos, imag_cos)
result = (7 * z + 1 - cos_pi_z * (5 * z + 2)) / 4
if np.isnan(result.real) or np.isnan(result.imag) or np.isinf(result.real) or np.isinf(result.imag):
return z
return result
except:
return z
def iterate_point(self, z0: complex) -> Tuple[int, float]:
z = z0
last_abs = abs(z)
for i in range(self.params.max_iter):
z = self.calculate_next_z(z)
current_abs = abs(z)
if current_abs > self.params.escape_radius:
return i, current_abs
if abs(current_abs - last_abs) < 1e-10:
return i, current_abs
if np.isnan(current_abs) or np.isinf(current_abs):
return i, self.params.escape_radius
last_abs = current_abs
return self.params.max_iter, abs(z)
def generate(self) -> np.ndarray:
x = np.linspace(self.params.xmin, self.params.xmax, self.params.width)
y = np.linspace(self.params.ymin, self.params.ymax, self.params.height)
data = np.zeros((self.params.width, self.params.height), dtype=float)
for i in tqdm(range(self.params.width)):
for j in range(self.params.height):
z0 = x[i] + 1j * y[j]
iters, magnitude = self.iterate_point(z0)
if iters < self.params.max_iter:
log_mag = np.log2(max(1.0, magnitude))
data[i, j] = iters + 1 - min(1.0, log_mag / np.log2(self.params.escape_radius))
if data.max() > 0:
data = data / self.params.max_iter
data = np.power(data, 0.5)
return data
def plot_fractal(data: np.ndarray, params: FractalParams, title: str, is_collatz: bool = False):
plt.figure(figsize=(15, 15))
if data.max() > 0:
data = data / params.max_iter
data = np.power(data, 0.4)
cmap = LinearSegmentedColormap.from_list('fractal', params.palette_colors)
plt.imshow(data.T, cmap=cmap, extent=[params.xmin, params.xmax, params.ymin, params.ymax])
plt.grid(True, alpha=0.3)
plt.xlabel('Re(z)' if is_collatz else 'Re(c)')
plt.ylabel('Im(z)' if is_collatz else 'Im(c)')
plt.title(f"{title}\nx: [{params.xmin:.10f}, {params.xmax:.10f}]\ny: [{params.ymin:.10f}, {params.ymax:.10f}]")
plt.colorbar(label='Iteration count')
plt.show()
def generate_views():
palettes = [
[(0, 0, 0), (0.5, 0, 0), (1, 0, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1)],
[(0, 0, 0), (0.2, 0, 0.4), (0.4, 0, 0.8), (0.8, 0.2, 0.8), (1, 0.4, 0.4), (1, 1, 1)],
[(0, 0, 0.1), (0, 0.2, 0.4), (0, 0.4, 0.8), (0, 0.8, 1), (0.5, 1, 1), (1, 1, 1)]
]
mandelbrot_views = [
(-0.17, -0.145, -1.045, -1.02),
(-0.15378, -0.15373, -1.03041, -1.03036),
(-0.041747375, -0.04174175, -0.9847536875, -0.9847480625)
]
for i, view in enumerate(mandelbrot_views):
for j, palette in enumerate(palettes):
params = FractalParams(
xmin=view[0], xmax=view[1],
ymin=view[2], ymax=view[3],
max_iter=1000,
palette_colors=palette
)
mandel = MandelbrotSet(params)
data = mandel.generate()
plot_fractal(data, params, f"Mandelbrot View {i+1} - Palette {j+1}")
collatz_params = FractalParams(
xmin=-0.17, xmax=-0.145,
ymin=-1.045, ymax=-1.02,
max_iter=100,
palette_colors=palettes[1]
)
collatz = CollatzFractal(collatz_params)
data = collatz.generate()
plot_fractal(data, collatz_params, "Collatz Fractal", True)
generate_views()
0%| | 0/2000 [00:00<?, ?it/s]
In [ ]:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from dataclasses import dataclass
from typing import Tuple, List
from tqdm.notebook import tqdm
@dataclass
class FractalParams:
width: int = 2000
height: int = 2000
xmin: float = -2.0
xmax: float = 2.0
ymin: float = -2.0
ymax: float = 2.0
max_iter: int = 100
escape_radius: float = 8.0
palette_colors: List[Tuple[float, float, float]] = None
def __post_init__(self):
if self.palette_colors is None:
self.palette_colors = [
(0, 0, 0), (0.2, 0, 0.4), (0.4, 0, 0.8),
(0.8, 0.2, 0.8), (1, 0.4, 0.4), (1, 1, 1)
]
class CollatzFractalOriginal:
def __init__(self, params: FractalParams):
self.params = params
def calculate_next_z(self, z: complex) -> complex:
try:
if abs(z) > self.params.escape_radius:
return z
real_cos = np.cos(np.pi * z.real) * np.cosh(np.pi * z.imag)
imag_cos = -np.sin(np.pi * z.real) * np.sinh(np.pi * z.imag)
cos_pi_z = complex(real_cos, imag_cos)
result = (7 * z + 1 - cos_pi_z * (5 * z - 2)) / 4
if np.isnan(result.real) or np.isnan(result.imag) or np.isinf(result.real) or np.isinf(result.imag):
return z
return result
except:
return z
def iterate_point(self, z0: complex) -> Tuple[int, float]:
z = z0
last_abs = abs(z)
for i in range(self.params.max_iter):
z = self.calculate_next_z(z)
current_abs = abs(z)
if current_abs > self.params.escape_radius:
return i, current_abs
if abs(current_abs - last_abs) < 1e-10:
return i, current_abs
if np.isnan(current_abs) or np.isinf(current_abs):
return i, self.params.escape_radius
last_abs = current_abs
return self.params.max_iter, abs(z)
def generate(self) -> np.ndarray:
x = np.linspace(self.params.xmin, self.params.xmax, self.params.width)
y = np.linspace(self.params.ymin, self.params.ymax, self.params.height)
data = np.zeros((self.params.width, self.params.height), dtype=float)
for i in tqdm(range(self.params.width)):
for j in range(self.params.height):
z0 = x[i] + 1j * y[j]
iters, magnitude = self.iterate_point(z0)
if iters < self.params.max_iter:
log_mag = np.log2(max(1.0, magnitude))
data[i, j] = iters + 1 - min(1.0, log_mag / np.log2(self.params.escape_radius))
if data.max() > 0:
data = data / self.params.max_iter
data = np.power(data, 0.5)
return data
class CollatzFractalNew:
def __init__(self, params: FractalParams):
self.params = params
def calculate_next_z(self, z: complex) -> complex:
try:
if abs(z) > self.params.escape_radius:
return z
real_cos = np.cos(np.pi * z.real) * np.cosh(np.pi * z.imag)
imag_cos = -np.sin(np.pi * z.real) * np.sinh(np.pi * z.imag)
cos_pi_z = complex(real_cos, imag_cos)
result = (7 * z + 2 - cos_pi_z * (5 * z + 2)) / 4
if np.isnan(result.real) or np.isnan(result.imag) or np.isinf(result.real) or np.isinf(result.imag):
return z
return result
except:
return z
def iterate_point(self, z0: complex) -> Tuple[int, float]:
z = z0
last_abs = abs(z)
for i in range(self.params.max_iter):
z = self.calculate_next_z(z)
current_abs = abs(z)
if current_abs > self.params.escape_radius:
return i, current_abs
if abs(current_abs - last_abs) < 1e-10:
return i, current_abs
if np.isnan(current_abs) or np.isinf(current_abs):
return i, self.params.escape_radius
last_abs = current_abs
return self.params.max_iter, abs(z)
def generate(self) -> np.ndarray:
x = np.linspace(self.params.xmin, self.params.xmax, self.params.width)
y = np.linspace(self.params.ymin, self.params.ymax, self.params.height)
data = np.zeros((self.params.width, self.params.height), dtype=float)
for i in tqdm(range(self.params.width)):
for j in range(self.params.height):
z0 = x[i] + 1j * y[j]
iters, magnitude = self.iterate_point(z0)
if iters < self.params.max_iter:
log_mag = np.log2(max(1.0, magnitude))
data[i, j] = iters + 1 - min(1.0, log_mag / np.log2(self.params.escape_radius))
if data.max() > 0:
data = data / self.params.max_iter
data = np.power(data, 0.5)
return data
def plot_fractal(data: np.ndarray, params: FractalParams, title: str):
plt.figure(figsize=(15, 15))
if data.max() > 0:
data = data / params.max_iter
data = np.power(data, 0.4)
cmap = LinearSegmentedColormap.from_list('fractal', params.palette_colors)
plt.imshow(data.T, cmap=cmap, extent=[params.xmin, params.xmax, params.ymin, params.ymax])
plt.grid(True, alpha=0.3)
plt.xlabel('Re(z)')
plt.ylabel('Im(z)')
plt.title(f"{title}\nx: [{params.xmin:.6f}, {params.xmax:.6f}]\ny: [{params.ymin:.6f}, {params.ymax:.6f}]")
plt.colorbar(label='Iteration count')
plt.show()
def generate_comparison_views():
palettes = [
[(0, 0, 0), (0.2, 0, 0.4), (0.4, 0, 0.8), (0.8, 0.2, 0.8), (1, 0.4, 0.4), (1, 1, 1)],
[(0, 0, 0), (0.5, 0, 0), (1, 0, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1)],
[(0, 0, 0.1), (0, 0.2, 0.4), (0, 0.4, 0.8), (0, 0.8, 1), (0.5, 1, 1), (1, 1, 1)]
]
views = [
{'xmin': -2.0, 'xmax': 2.0, 'ymin': -2.0, 'ymax': 2.0, 'title': 'Full View', 'max_iter': 100},
{'xmin': -1.0, 'xmax': 1.0, 'ymin': -1.0, 'ymax': 1.0, 'title': 'Zoom 1', 'max_iter': 150},
{'xmin': -0.5, 'xmax': 0.5, 'ymin': -0.5, 'ymax': 0.5, 'title': 'Zoom 2', 'max_iter': 200},
{'xmin': -0.2, 'xmax': 0.2, 'ymin': -0.2, 'ymax': 0.2, 'title': 'Zoom 3', 'max_iter': 250},
{'xmin': -0.1, 'xmax': 0.1, 'ymin': -0.1, 'ymax': 0.1, 'title': 'Zoom 4', 'max_iter': 300}
]
for view in views:
for i, palette in enumerate(palettes):
params = FractalParams(
width=2000, height=2000,
xmin=view['xmin'], xmax=view['xmax'],
ymin=view['ymin'], ymax=view['ymax'],
max_iter=view['max_iter'],
palette_colors=palette
)
print(f"\nGenerating Original Formula - {view['title']} with palette {i+1}")
collatz_orig = CollatzFractalOriginal(params)
data = collatz_orig.generate()
plot_fractal(data, params, f"Original Collatz - {view['title']} - Palette {i+1}")
print(f"\nGenerating New Formula - {view['title']} with palette {i+1}")
collatz_new = CollatzFractalNew(params)
data = collatz_new.generate()
plot_fractal(data, params, f"New Collatz - {view['title']} - Palette {i+1}")
generate_comparison_views()
Generating Original Formula - Full View with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Full View with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Full View with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Full View with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Full View with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Full View with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 1 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 1 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 1 with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 1 with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 1 with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 1 with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 2 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 2 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 2 with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 2 with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 2 with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 2 with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 3 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 3 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 3 with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 3 with palette 2
0%| | 0/2000 [00:00<?, ?it/s]
Generating Original Formula - Zoom 3 with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
Generating New Formula - Zoom 3 with palette 3
0%| | 0/2000 [00:00<?, ?it/s]
In [ ]:
def generate_comparison_views():
palettes = [
[(0, 0, 0), (0.2, 0, 0.4), (0.4, 0, 0.8), (0.8, 0.2, 0.8), (1, 0.4, 0.4), (1, 1, 1)],
[(0, 0, 0), (0.5, 0, 0), (1, 0, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1)],
[(0, 0, 0.1), (0, 0.2, 0.4), (0, 0.4, 0.8), (0, 0.8, 1), (0.5, 1, 1), (1, 1, 1)]
]
views = [
{'xmin': -0.1, 'xmax': 0.1, 'ymin': -0.1, 'ymax': 0.1, 'title': 'Zoom 4', 'max_iter': 300}
]
for view in views:
for i, palette in enumerate(palettes):
params = FractalParams(
width=2000, height=2000,
xmin=view['xmin'], xmax=view['xmax'],
ymin=view['ymin'], ymax=view['ymax'],
max_iter=view['max_iter'],
palette_colors=palette
)
print(f"\nGenerating Original Formula - {view['title']} with palette {i+1}")
collatz_orig = CollatzFractalOriginal(params)
data = collatz_orig.generate()
plot_fractal(data, params, f"Original Collatz - {view['title']} - Palette {i+1}")
print(f"\nGenerating New Formula - {view['title']} with palette {i+1}")
collatz_new = CollatzFractalNew(params)
data = collatz_new.generate()
plot_fractal(data, params, f"New Collatz - {view['title']} - Palette {i+1}")
generate_comparison_views()
Generating Original Formula - Zoom 4 with palette 1
0%| | 0/2000 [00:00<?, ?it/s]