|
| 1 | +"""Author Alexandre De Zotti |
| 2 | +
|
| 3 | +Draws Julia sets of quadratic polynomials and exponential maps. |
| 4 | + More specifically, this iterates the function a fixed number of times |
| 5 | + then plots whether the absolute value of the last iterate is greater than |
| 6 | + a fixed threshold (named "escape radius"). For the exponential map this is not |
| 7 | + really an escape radius but rather a convenient way to approximate the Julia |
| 8 | + set with bounded orbits. |
| 9 | +
|
| 10 | +The examples presented here are: |
| 11 | +- The Cauliflower Julia set, see e.g. |
| 12 | +https://en.wikipedia.org/wiki/File:Julia_z2%2B0,25.png |
| 13 | +- Other examples from https://en.wikipedia.org/wiki/Julia_set |
| 14 | +- An exponential map Julia set, ambiantly homeomorphic to the examples in |
| 15 | +http://www.math.univ-toulouse.fr/~cheritat/GalII/galery.html |
| 16 | + and |
| 17 | +https://ddd.uab.cat/pub/pubmat/02141493v43n1/02141493v43n1p27.pdf |
| 18 | +
|
| 19 | +Remark: Some overflow runtime warnings are suppressed. This is because of the |
| 20 | + way the iteration loop is implemented, using numpy's efficient computations. |
| 21 | + Overflows and infinites are replaced after each step by a large number. |
| 22 | +""" |
| 23 | + |
| 24 | +import warnings |
| 25 | +from typing import Any, Callable |
| 26 | + |
| 27 | +import numpy |
| 28 | +from matplotlib import pyplot |
| 29 | + |
| 30 | +c_cauliflower = 0.25 + 0.0j |
| 31 | +c_polynomial_1 = -0.4 + 0.6j |
| 32 | +c_polynomial_2 = -0.1 + 0.651j |
| 33 | +c_exponential = -2.0 |
| 34 | +nb_iterations = 56 |
| 35 | +window_size = 2.0 |
| 36 | +nb_pixels = 666 |
| 37 | + |
| 38 | + |
| 39 | +def eval_exponential(c_parameter: complex, z_values: numpy.ndarray) -> numpy.ndarray: |
| 40 | + """ |
| 41 | + Evaluate $e^z + c$. |
| 42 | + >>> eval_exponential(0, 0) |
| 43 | + 1.0 |
| 44 | + >>> abs(eval_exponential(1, numpy.pi*1.j)) < 1e-15 |
| 45 | + True |
| 46 | + >>> abs(eval_exponential(1.j, 0)-1-1.j) < 1e-15 |
| 47 | + True |
| 48 | + """ |
| 49 | + return numpy.exp(z_values) + c_parameter |
| 50 | + |
| 51 | + |
| 52 | +def eval_quadratic_polynomial( |
| 53 | + c_parameter: complex, z_values: numpy.ndarray |
| 54 | +) -> numpy.ndarray: |
| 55 | + """ |
| 56 | + >>> eval_quadratic_polynomial(0, 2) |
| 57 | + 4 |
| 58 | + >>> eval_quadratic_polynomial(-1, 1) |
| 59 | + 0 |
| 60 | + >>> round(eval_quadratic_polynomial(1.j, 0).imag) |
| 61 | + 1 |
| 62 | + >>> round(eval_quadratic_polynomial(1.j, 0).real) |
| 63 | + 0 |
| 64 | + """ |
| 65 | + return z_values * z_values + c_parameter |
| 66 | + |
| 67 | + |
| 68 | +def prepare_grid(window_size: float, nb_pixels: int) -> numpy.ndarray: |
| 69 | + """ |
| 70 | + Create a grid of complex values of size nb_pixels*nb_pixels with real and |
| 71 | + imaginary parts ranging from -window_size to window_size (inclusive). |
| 72 | + Returns a numpy array. |
| 73 | +
|
| 74 | + >>> prepare_grid(1,3) |
| 75 | + array([[-1.-1.j, -1.+0.j, -1.+1.j], |
| 76 | + [ 0.-1.j, 0.+0.j, 0.+1.j], |
| 77 | + [ 1.-1.j, 1.+0.j, 1.+1.j]]) |
| 78 | + """ |
| 79 | + x = numpy.linspace(-window_size, window_size, nb_pixels) |
| 80 | + x = x.reshape((nb_pixels, 1)) |
| 81 | + y = numpy.linspace(-window_size, window_size, nb_pixels) |
| 82 | + y = y.reshape((1, nb_pixels)) |
| 83 | + return x + 1.0j * y |
| 84 | + |
| 85 | + |
| 86 | +def iterate_function( |
| 87 | + eval_function: Callable[[Any, numpy.ndarray], numpy.ndarray], |
| 88 | + function_params: Any, |
| 89 | + nb_iterations: int, |
| 90 | + z_0: numpy.ndarray, |
| 91 | + infinity: float = None, |
| 92 | +) -> numpy.ndarray: |
| 93 | + """ |
| 94 | + Iterate the function "eval_function" exactly nb_iterations times. |
| 95 | + The first argument of the function is a parameter which is contained in |
| 96 | + function_params. The variable z_0 is an array that contains the initial |
| 97 | + values to iterate from. |
| 98 | + This function returns the final iterates. |
| 99 | +
|
| 100 | + >>> iterate_function(eval_quadratic_polynomial, 0, 3, numpy.array([0,1,2])).shape |
| 101 | + (3,) |
| 102 | + >>> numpy.round(iterate_function(eval_quadratic_polynomial, |
| 103 | + ... 0, |
| 104 | + ... 3, |
| 105 | + ... numpy.array([0,1,2]))[0]) |
| 106 | + 0j |
| 107 | + >>> numpy.round(iterate_function(eval_quadratic_polynomial, |
| 108 | + ... 0, |
| 109 | + ... 3, |
| 110 | + ... numpy.array([0,1,2]))[1]) |
| 111 | + (1+0j) |
| 112 | + >>> numpy.round(iterate_function(eval_quadratic_polynomial, |
| 113 | + ... 0, |
| 114 | + ... 3, |
| 115 | + ... numpy.array([0,1,2]))[2]) |
| 116 | + (256+0j) |
| 117 | + """ |
| 118 | + |
| 119 | + z_n = z_0.astype("complex64") |
| 120 | + for i in range(nb_iterations): |
| 121 | + z_n = eval_function(function_params, z_n) |
| 122 | + if infinity is not None: |
| 123 | + numpy.nan_to_num(z_n, copy=False, nan=infinity) |
| 124 | + z_n[abs(z_n) == numpy.inf] = infinity |
| 125 | + return z_n |
| 126 | + |
| 127 | + |
| 128 | +def show_results( |
| 129 | + function_label: str, |
| 130 | + function_params: Any, |
| 131 | + escape_radius: float, |
| 132 | + z_final: numpy.ndarray, |
| 133 | +) -> None: |
| 134 | + """ |
| 135 | + Plots of whether the absolute value of z_final is greater than |
| 136 | + the value of escape_radius. Adds the function_label and function_params to |
| 137 | + the title. |
| 138 | +
|
| 139 | + >>> show_results('80', 0, 1, numpy.array([[0,1,.5],[.4,2,1.1],[.2,1,1.3]])) |
| 140 | + """ |
| 141 | + |
| 142 | + abs_z_final = (abs(z_final)).transpose() |
| 143 | + abs_z_final[:, :] = abs_z_final[::-1, :] |
| 144 | + pyplot.matshow(abs_z_final < escape_radius) |
| 145 | + pyplot.title(f"Julia set of ${function_label}$, $c={function_params}$") |
| 146 | + pyplot.show() |
| 147 | + |
| 148 | + |
| 149 | +def ignore_overflow_warnings() -> None: |
| 150 | + """ |
| 151 | + Ignore some overflow and invalid value warnings. |
| 152 | +
|
| 153 | + >>> ignore_overflow_warnings() |
| 154 | + """ |
| 155 | + warnings.filterwarnings( |
| 156 | + "ignore", category=RuntimeWarning, message="overflow encountered in multiply" |
| 157 | + ) |
| 158 | + warnings.filterwarnings( |
| 159 | + "ignore", |
| 160 | + category=RuntimeWarning, |
| 161 | + message="invalid value encountered in multiply", |
| 162 | + ) |
| 163 | + warnings.filterwarnings( |
| 164 | + "ignore", category=RuntimeWarning, message="overflow encountered in absolute" |
| 165 | + ) |
| 166 | + warnings.filterwarnings( |
| 167 | + "ignore", category=RuntimeWarning, message="overflow encountered in exp" |
| 168 | + ) |
| 169 | + |
| 170 | + |
| 171 | +if __name__ == "__main__": |
| 172 | + |
| 173 | + z_0 = prepare_grid(window_size, nb_pixels) |
| 174 | + |
| 175 | + ignore_overflow_warnings() # See file header for explanations |
| 176 | + |
| 177 | + nb_iterations = 24 |
| 178 | + escape_radius = 2 * abs(c_cauliflower) + 1 |
| 179 | + z_final = iterate_function( |
| 180 | + eval_quadratic_polynomial, |
| 181 | + c_cauliflower, |
| 182 | + nb_iterations, |
| 183 | + z_0, |
| 184 | + infinity=1.1 * escape_radius, |
| 185 | + ) |
| 186 | + show_results("z^2+c", c_cauliflower, escape_radius, z_final) |
| 187 | + |
| 188 | + nb_iterations = 64 |
| 189 | + escape_radius = 2 * abs(c_polynomial_1) + 1 |
| 190 | + z_final = iterate_function( |
| 191 | + eval_quadratic_polynomial, |
| 192 | + c_polynomial_1, |
| 193 | + nb_iterations, |
| 194 | + z_0, |
| 195 | + infinity=1.1 * escape_radius, |
| 196 | + ) |
| 197 | + show_results("z^2+c", c_polynomial_1, escape_radius, z_final) |
| 198 | + |
| 199 | + nb_iterations = 161 |
| 200 | + escape_radius = 2 * abs(c_polynomial_2) + 1 |
| 201 | + z_final = iterate_function( |
| 202 | + eval_quadratic_polynomial, |
| 203 | + c_polynomial_2, |
| 204 | + nb_iterations, |
| 205 | + z_0, |
| 206 | + infinity=1.1 * escape_radius, |
| 207 | + ) |
| 208 | + show_results("z^2+c", c_polynomial_2, escape_radius, z_final) |
| 209 | + |
| 210 | + nb_iterations = 12 |
| 211 | + escape_radius = 10000.0 |
| 212 | + z_final = iterate_function( |
| 213 | + eval_exponential, |
| 214 | + c_exponential, |
| 215 | + nb_iterations, |
| 216 | + z_0 + 2, |
| 217 | + infinity=1.0e10, |
| 218 | + ) |
| 219 | + show_results("e^z+c", c_exponential, escape_radius, z_final) |
0 commit comments