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)
No description has been provided for this image
In [13]:
params = JuliaParams(c=-0.8 + 0.156j)
fractal = JuliaSet(params)
data = fractal.generate()
fractal.plot(data)
No description has been provided for this image
In [14]:
params = NewtonParams(power=5)
fractal = NewtonFractal(params)
data = fractal.generate()
fractal.plot(data)
No description has been provided for this image
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...
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Generating fractals with Cyclone palette...
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Generating fractals with Burning Ship palette...
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Generating fractals with Electric Mold palette...
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Generating fractals with Fork Valley palette...
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
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)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
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]
No description has been provided for this image
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]
No description has been provided for this image
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]
No description has been provided for this image
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]
No description has been provided for this image
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]
No description has been provided for this image
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()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
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]
No description has been provided for this image
Generating New Formula - Full View with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Full View with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Full View with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Full View with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Full View with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 1 with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 1 with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 1 with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 1 with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 1 with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 1 with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 2 with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 2 with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 2 with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 2 with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 2 with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 2 with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 3 with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 3 with palette 1
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 3 with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating New Formula - Zoom 3 with palette 2
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
Generating Original Formula - Zoom 3 with palette 3
  0%|          | 0/2000 [00:00<?, ?it/s]
No description has been provided for this image
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]