Source code for PyMandelbrot.mandelbrot

""" This module implements the base class to compute the Mandelbrot dynamics. """
import warnings
import numpy as np


[docs]class MandelbrotDynamics: """ Implementation of the f(z) = z**2 + c function for the Mandelbrot Dynamics. Examples -------- One Mandelbrot dynamics iteration: >>> import numpy as np >>> from PyMandelbrot.mandelbrot import MandelbrotDynamics >>> cs = np.linspace(-1,1,5) # offset parameter >>> cs array([-1. , -0.5, 0. , 0.5, 1. ]) >>> z0s = np.zeros_like(cs) # starting point >>> z0s array([0., 0., 0., 0., 0.]) >>> dynamics = MandelbrotDynamics(z0s, cs) >>> z1s = dynamics() >>> z1s array([-1. , -0.5, 0. , 0.5, 1. ]) ``n = 4`` iterations: >>> z4s = dynamics.n_steps(n=4) >>> z4s array([ 0. , -0.30859, 0. , 1.62890, 26. ]) Divergency speed calculation: >>> it = dynamics.get_divergency_iter() >>> it array([20, 20, 20, 5, 2], dtype=int16) """ def __init__( self, z0: np.ndarray = None, c: np.ndarray = None, clip_value: float = 4.0 ): """Basic constructor of the complex function describing Mandelbrot set. Parameters ---------- z0 : np.ndarray Starting points of the Mandelbrot dynamics. c : np.array Offset parameters of the Mandelbrot function. clip_value : float The value to clip the starting point real and imaginary parts with. Defaults to 4. """ self.__z0 = z0 self.__c = c self.fn = lambda z, c: z**2 + c self.clip_value = clip_value @property def z0(self) -> np.ndarray: """Starting point property.""" return self.__z0 @z0.setter def z0(self, values: np.ndarray): """Starting point property setter. Checks that all the points have real and imaginary party lower than a certain value in the complex plane. Clips exeeding values otherwise. Parameters ---------- values : np.ndarray Starting points of the Mandelbrot dynamics. """ real = np.clip(np.real(values), -self.clip_value, self.clip_value) imag = np.clip(np.imag(values), -self.clip_value, self.clip_value) self.__z0 = real + 1j * imag
[docs] def reset_z0(self): """Resets the starting point to `None`.""" self.__z0 = None
@property def c(self) -> np.ndarray: """Offset parameter property.""" return self.__c @c.setter def c(self, values: np.ndarray): """Offset parameter property setter. Checks that all the points have real and imaginary party lower than a certain value in the complex plane. Clips exeeding values otherwise. Parameters ---------- values: np.ndarray Offset parameters of the Mandelbrot dynamics. """ real = np.clip(np.real(values), -self.clip_value, self.clip_value) imag = np.clip(np.imag(values), -self.clip_value, self.clip_value) self.__c = real + 1j * imag
[docs] def reset_c(self): """Resets the offset parameter to `None`.""" self.__c = None
def __call__(self, z0: np.ndarray = None, c: np.ndarray = None) -> np.ndarray: """Computes one step of the Mandelbrot dynamics for the given starting point. Parameters ---------- z0 : np.ndarray Starting points of the Mandelbrot dynamics. c : np.ndarray Offset parameters of the Mandelbrot dynamics. Returns ------- np.ndarray The computed n-times repeated Mandelbrot points. """ if z0 is None or c is None: self.check_params() z0 = z0 if z0 is not None else self.__z0 c = c if c is not None else self.__c with warnings.catch_warnings(): warnings.simplefilter("ignore") z = self.fn(z0, c) return z
[docs] def check_params(self): """Checks parameters are set. Raises ------ ValueError If either starting point `z0` or offset parameter `c` is `None`. """ if self.__z0 is None or self.__c is None: raise ValueError("Starting point not set, please set z0 attribute")
[docs] def n_steps(self, n: int): """Computes `n` steps of the Mandelbrot dynamics for the given starting point. Parameters ---------- z0 : np.ndarray Starting points of the Mandelbrot dynamic. n : int Number of steps of the Mandelbrot dynamics. """ z = self.__z0 for _ in range(n): z = self.__call__(z, self.__c) return z
[docs] def get_divergency_iter( self, threshold: float = 4.0, max_iter: float = 20 ) -> np.ndarray: """Computes the number of iteration needed by the dynamics to exceed some threshold. Given the stored starting point, it computes the number of steps for the dynamics modulusu to exceed a given threshold. Parameters ---------- threshold : float The threshold above which the dynamics is considered divergent. max_iter : int The maximum number of steps to be evaluated. Returns ------- np.ndarray The iteration number at which the dynamics has diverged. """ z = self.__z0 img = np.zeros_like(self.z0, dtype=np.int16) for _ in range(max_iter): z = self.__call__(z, self.__c) with warnings.catch_warnings(): warnings.simplefilter("ignore") mask = (np.absolute(z) > threshold).astype(int) img += mask return 20 - img