Skip to content

Commit 02bc4bf

Browse files
alexn11poyea
andauthored
Add Julia sets to fractals (TheAlgorithms#4382)
* Added Julia sets drawing * Forgot the .py extension * Update julia_sets.py Added online sources for comparison. Added more examples of fractal Julia sets. Added all type hints. Only show one picture Silented RuntuleWarning's (there's no way of avoiding them and they're not an issue per se) * Added doctest example for "show_results" * Filtering Nan's and infinites * added 1 missing type hint * in iterate_function, convert to dtype=complex64 * RuntimeWarning (fine) filtering * Type hint, test for ignore_warnings function, typo in header * Update julia_sets.py Type of expected output value for iterate function int array -> complex array (throws an error on test) * Update julia_sets.py - More accurate type for tests cases in eval_quadratic_polynomial and iterate_function - added more characters for variables c & z in eval_quadratic_polynomial and eval_exponential to silent bot warnings * Function def formatting Blocked by black * Update julia_sets.py * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> * added more doctests for eval_exponential * Update fractals/julia_sets.py Co-authored-by: John Law <[email protected]> Co-authored-by: John Law <[email protected]>
1 parent 5d02103 commit 02bc4bf

File tree

1 file changed

+219
-0
lines changed

1 file changed

+219
-0
lines changed

fractals/julia_sets.py

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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

Comments
 (0)