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