Generating Julia set
So, I just wanted to post something on Rosetta Code, and I found this task of generating and plotting a Julia set: http://www.rosettacode.org/wiki/Julia_set. There was already one solution but it was quite inefficient and not Pythonic. Here is my attempt on this:
"""
This solution is an improved version of an efficient Julia set solver
from:
'Bauckhage C. NumPy/SciPy Recipes for Image Processing:
Creating Fractal Images. researchgate. net, Feb. 2015.'
"""
import itertools
from functools import partial
from numbers import Complex
from typing import Callable
import matplotlib.pyplot as plt
import numpy as np
def douady_hubbard_polynomial(z: Complex,
*,
c: Complex):
"""
Monic and centered quadratic complex polynomial
https://en.wikipedia.org/wiki/Complex_quadratic_polynomial#Map
"""
return z ** 2 + c
def julia_set(*,
mapping: Callable[[Complex], Complex],
min_coordinate: Complex,
max_coordinate: Complex,
width: int,
height: int,
iterations_count: int = 256,
threshold: float = 2.) -> np.ndarray:
"""
As described in https://en.wikipedia.org/wiki/Julia_set
:param mapping: function defining Julia set
:param min_coordinate: bottom-left complex plane coordinate
:param max_coordinate: upper-right complex plane coordinate
:param height: pixels in vertical axis
:param width: pixels in horizontal axis
:param iterations_count: number of iterations
:param threshold: if the magnitude of z becomes greater
than the threshold we assume that it will diverge to infinity
:return: 2D pixels array of intensities
"""
imaginary_axis, real_axis = np.ogrid[
min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
complex_plane = real_axis + 1j * imaginary_axis
result = np.ones(complex_plane.shape)
for _ in itertools.repeat(None, iterations_count):
mask = np.abs(complex_plane) <= threshold
if not mask.any():
break
complex_plane[mask] = mapping(complex_plane[mask])
result[~mask] += 1
return result
if __name__ == '__main__':
mapping = partial(douady_hubbard_polynomial,
c=-0.7 + 0.27015j) # type: Callable[[Complex], Complex]
image = julia_set(mapping=mapping,
min_coordinate=-1.5 - 1j,
max_coordinate=1.5 + 1j,
width=800,
height=600)
plt.axis('off')
plt.imshow(image,
cmap='nipy_spectral',
origin='lower')
plt.show()
I think it looks good, and it is definitely more efficient. There was just one thing that I was not sure about. I was thinking to take out creating a complex_plane to a separate function and pass it as a parameter to julia_set. But in this case the julia_set wouldn't be a pure function as it would mutate the complex_plane. And I prefer my functions not to have any side effects. So I decided to leave it as is.
Any comments on this matter or anything else are welcome.
Here are some examples of output:


python numpy fractals
add a comment |
So, I just wanted to post something on Rosetta Code, and I found this task of generating and plotting a Julia set: http://www.rosettacode.org/wiki/Julia_set. There was already one solution but it was quite inefficient and not Pythonic. Here is my attempt on this:
"""
This solution is an improved version of an efficient Julia set solver
from:
'Bauckhage C. NumPy/SciPy Recipes for Image Processing:
Creating Fractal Images. researchgate. net, Feb. 2015.'
"""
import itertools
from functools import partial
from numbers import Complex
from typing import Callable
import matplotlib.pyplot as plt
import numpy as np
def douady_hubbard_polynomial(z: Complex,
*,
c: Complex):
"""
Monic and centered quadratic complex polynomial
https://en.wikipedia.org/wiki/Complex_quadratic_polynomial#Map
"""
return z ** 2 + c
def julia_set(*,
mapping: Callable[[Complex], Complex],
min_coordinate: Complex,
max_coordinate: Complex,
width: int,
height: int,
iterations_count: int = 256,
threshold: float = 2.) -> np.ndarray:
"""
As described in https://en.wikipedia.org/wiki/Julia_set
:param mapping: function defining Julia set
:param min_coordinate: bottom-left complex plane coordinate
:param max_coordinate: upper-right complex plane coordinate
:param height: pixels in vertical axis
:param width: pixels in horizontal axis
:param iterations_count: number of iterations
:param threshold: if the magnitude of z becomes greater
than the threshold we assume that it will diverge to infinity
:return: 2D pixels array of intensities
"""
imaginary_axis, real_axis = np.ogrid[
min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
complex_plane = real_axis + 1j * imaginary_axis
result = np.ones(complex_plane.shape)
for _ in itertools.repeat(None, iterations_count):
mask = np.abs(complex_plane) <= threshold
if not mask.any():
break
complex_plane[mask] = mapping(complex_plane[mask])
result[~mask] += 1
return result
if __name__ == '__main__':
mapping = partial(douady_hubbard_polynomial,
c=-0.7 + 0.27015j) # type: Callable[[Complex], Complex]
image = julia_set(mapping=mapping,
min_coordinate=-1.5 - 1j,
max_coordinate=1.5 + 1j,
width=800,
height=600)
plt.axis('off')
plt.imshow(image,
cmap='nipy_spectral',
origin='lower')
plt.show()
I think it looks good, and it is definitely more efficient. There was just one thing that I was not sure about. I was thinking to take out creating a complex_plane to a separate function and pass it as a parameter to julia_set. But in this case the julia_set wouldn't be a pure function as it would mutate the complex_plane. And I prefer my functions not to have any side effects. So I decided to leave it as is.
Any comments on this matter or anything else are welcome.
Here are some examples of output:


python numpy fractals
add a comment |
So, I just wanted to post something on Rosetta Code, and I found this task of generating and plotting a Julia set: http://www.rosettacode.org/wiki/Julia_set. There was already one solution but it was quite inefficient and not Pythonic. Here is my attempt on this:
"""
This solution is an improved version of an efficient Julia set solver
from:
'Bauckhage C. NumPy/SciPy Recipes for Image Processing:
Creating Fractal Images. researchgate. net, Feb. 2015.'
"""
import itertools
from functools import partial
from numbers import Complex
from typing import Callable
import matplotlib.pyplot as plt
import numpy as np
def douady_hubbard_polynomial(z: Complex,
*,
c: Complex):
"""
Monic and centered quadratic complex polynomial
https://en.wikipedia.org/wiki/Complex_quadratic_polynomial#Map
"""
return z ** 2 + c
def julia_set(*,
mapping: Callable[[Complex], Complex],
min_coordinate: Complex,
max_coordinate: Complex,
width: int,
height: int,
iterations_count: int = 256,
threshold: float = 2.) -> np.ndarray:
"""
As described in https://en.wikipedia.org/wiki/Julia_set
:param mapping: function defining Julia set
:param min_coordinate: bottom-left complex plane coordinate
:param max_coordinate: upper-right complex plane coordinate
:param height: pixels in vertical axis
:param width: pixels in horizontal axis
:param iterations_count: number of iterations
:param threshold: if the magnitude of z becomes greater
than the threshold we assume that it will diverge to infinity
:return: 2D pixels array of intensities
"""
imaginary_axis, real_axis = np.ogrid[
min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
complex_plane = real_axis + 1j * imaginary_axis
result = np.ones(complex_plane.shape)
for _ in itertools.repeat(None, iterations_count):
mask = np.abs(complex_plane) <= threshold
if not mask.any():
break
complex_plane[mask] = mapping(complex_plane[mask])
result[~mask] += 1
return result
if __name__ == '__main__':
mapping = partial(douady_hubbard_polynomial,
c=-0.7 + 0.27015j) # type: Callable[[Complex], Complex]
image = julia_set(mapping=mapping,
min_coordinate=-1.5 - 1j,
max_coordinate=1.5 + 1j,
width=800,
height=600)
plt.axis('off')
plt.imshow(image,
cmap='nipy_spectral',
origin='lower')
plt.show()
I think it looks good, and it is definitely more efficient. There was just one thing that I was not sure about. I was thinking to take out creating a complex_plane to a separate function and pass it as a parameter to julia_set. But in this case the julia_set wouldn't be a pure function as it would mutate the complex_plane. And I prefer my functions not to have any side effects. So I decided to leave it as is.
Any comments on this matter or anything else are welcome.
Here are some examples of output:


python numpy fractals
So, I just wanted to post something on Rosetta Code, and I found this task of generating and plotting a Julia set: http://www.rosettacode.org/wiki/Julia_set. There was already one solution but it was quite inefficient and not Pythonic. Here is my attempt on this:
"""
This solution is an improved version of an efficient Julia set solver
from:
'Bauckhage C. NumPy/SciPy Recipes for Image Processing:
Creating Fractal Images. researchgate. net, Feb. 2015.'
"""
import itertools
from functools import partial
from numbers import Complex
from typing import Callable
import matplotlib.pyplot as plt
import numpy as np
def douady_hubbard_polynomial(z: Complex,
*,
c: Complex):
"""
Monic and centered quadratic complex polynomial
https://en.wikipedia.org/wiki/Complex_quadratic_polynomial#Map
"""
return z ** 2 + c
def julia_set(*,
mapping: Callable[[Complex], Complex],
min_coordinate: Complex,
max_coordinate: Complex,
width: int,
height: int,
iterations_count: int = 256,
threshold: float = 2.) -> np.ndarray:
"""
As described in https://en.wikipedia.org/wiki/Julia_set
:param mapping: function defining Julia set
:param min_coordinate: bottom-left complex plane coordinate
:param max_coordinate: upper-right complex plane coordinate
:param height: pixels in vertical axis
:param width: pixels in horizontal axis
:param iterations_count: number of iterations
:param threshold: if the magnitude of z becomes greater
than the threshold we assume that it will diverge to infinity
:return: 2D pixels array of intensities
"""
imaginary_axis, real_axis = np.ogrid[
min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
complex_plane = real_axis + 1j * imaginary_axis
result = np.ones(complex_plane.shape)
for _ in itertools.repeat(None, iterations_count):
mask = np.abs(complex_plane) <= threshold
if not mask.any():
break
complex_plane[mask] = mapping(complex_plane[mask])
result[~mask] += 1
return result
if __name__ == '__main__':
mapping = partial(douady_hubbard_polynomial,
c=-0.7 + 0.27015j) # type: Callable[[Complex], Complex]
image = julia_set(mapping=mapping,
min_coordinate=-1.5 - 1j,
max_coordinate=1.5 + 1j,
width=800,
height=600)
plt.axis('off')
plt.imshow(image,
cmap='nipy_spectral',
origin='lower')
plt.show()
I think it looks good, and it is definitely more efficient. There was just one thing that I was not sure about. I was thinking to take out creating a complex_plane to a separate function and pass it as a parameter to julia_set. But in this case the julia_set wouldn't be a pure function as it would mutate the complex_plane. And I prefer my functions not to have any side effects. So I decided to leave it as is.
Any comments on this matter or anything else are welcome.
Here are some examples of output:


python numpy fractals
python numpy fractals
asked Dec 24 '18 at 14:09
GeorgyGeorgy
6892517
6892517
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
1. Review
Some of the variable names could be improved:
complex_planeis an array of $z$ values for each pixel in the image, so naming itzwould help the reader relate it to thezindouady_hubbard_polynomial.
imaginary_axisandreal_axisare only used once in the very next line, so there is no need for them to have long and memorable names. I would use something short likeimandre.
resultis an array of iteration counts, so it could be named something likeiterations.
maskis a Boolean array selecting pixels that have not yet diverged to infinity, so something likenot_divergedorlivewould convey this better.
On each iteration, the iteration counts of the escaped pixels are incremented. This means that some pixels get incremented many times, for example a pixel that escapes on the first iteration gets its count incremented 256 times. It would be more efficient to set the iteration count for each pixel just once. A convenient time to do this is when it escapes.
As the number of iterations goes up, the number of pixels that have not escaped to infinity gets smaller and smaller. But the masking operations are always on the whole array. It would be more efficient to keep track of the indexes of the pixels that have not escaped, so that subsequent operations are on smaller and smaller arrays.
2. Revised code
im, re = np.ogrid[min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
z = (re + 1j * im).flatten()
live, = np.indices(z.shape) # indexes of pixels that have not escaped
iterations = np.empty_like(z, dtype=int)
for i in range(iterations_count):
z_live = z[live] = mapping(z[live])
escaped = abs(z_live) > threshold
iterations[live[escaped]] = i
live = live[~escaped]
iterations[live] = iterations_count - 1
return (iterations_count - iterations).reshape((height, width))
Notes
This is about three times as fast as the code in the post.
Because we are maintaining an array of indexes, it is convenient to flatten the
zarray and then reshapeiterationsto two dimensions before returning it. If we left the array two-dimensional, there would need to be two arrays of indexes,live_iandlive_j.Pixels that don't escape are given the value
iterations_count - 1in order to match the code in the post. It would make more sense to useiterations_countor a larger value here.The subtraction
iterations_count - iterationsis only there so that the returned values match the code in the post. The subtraction could be omitted if you reverse the colour map.
add a comment |
For douady_hubbard_polynomial, you're missing a return type.
This:
for _ in itertools.repeat(None, iterations_count):
can just be
for _ in range(iterations_count):
I don't see any other obvious issues.
3
itertools.repeat(None, iterations_count)is much faster for bigints since it doesn't require creating redundant objects, you can look at this answer
– Azat Ibrakov
Dec 24 '18 at 15:37
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
1
I did some timings, and it looks likeitertools.repeatwon't be of any help due to breaking out of a loop when themaskbecomes empty, so it won't reach bigints where the difference will be significant. I'm gonna change it torange.
– Georgy
Dec 24 '18 at 16:22
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210271%2fgenerating-julia-set%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
1. Review
Some of the variable names could be improved:
complex_planeis an array of $z$ values for each pixel in the image, so naming itzwould help the reader relate it to thezindouady_hubbard_polynomial.
imaginary_axisandreal_axisare only used once in the very next line, so there is no need for them to have long and memorable names. I would use something short likeimandre.
resultis an array of iteration counts, so it could be named something likeiterations.
maskis a Boolean array selecting pixels that have not yet diverged to infinity, so something likenot_divergedorlivewould convey this better.
On each iteration, the iteration counts of the escaped pixels are incremented. This means that some pixels get incremented many times, for example a pixel that escapes on the first iteration gets its count incremented 256 times. It would be more efficient to set the iteration count for each pixel just once. A convenient time to do this is when it escapes.
As the number of iterations goes up, the number of pixels that have not escaped to infinity gets smaller and smaller. But the masking operations are always on the whole array. It would be more efficient to keep track of the indexes of the pixels that have not escaped, so that subsequent operations are on smaller and smaller arrays.
2. Revised code
im, re = np.ogrid[min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
z = (re + 1j * im).flatten()
live, = np.indices(z.shape) # indexes of pixels that have not escaped
iterations = np.empty_like(z, dtype=int)
for i in range(iterations_count):
z_live = z[live] = mapping(z[live])
escaped = abs(z_live) > threshold
iterations[live[escaped]] = i
live = live[~escaped]
iterations[live] = iterations_count - 1
return (iterations_count - iterations).reshape((height, width))
Notes
This is about three times as fast as the code in the post.
Because we are maintaining an array of indexes, it is convenient to flatten the
zarray and then reshapeiterationsto two dimensions before returning it. If we left the array two-dimensional, there would need to be two arrays of indexes,live_iandlive_j.Pixels that don't escape are given the value
iterations_count - 1in order to match the code in the post. It would make more sense to useiterations_countor a larger value here.The subtraction
iterations_count - iterationsis only there so that the returned values match the code in the post. The subtraction could be omitted if you reverse the colour map.
add a comment |
1. Review
Some of the variable names could be improved:
complex_planeis an array of $z$ values for each pixel in the image, so naming itzwould help the reader relate it to thezindouady_hubbard_polynomial.
imaginary_axisandreal_axisare only used once in the very next line, so there is no need for them to have long and memorable names. I would use something short likeimandre.
resultis an array of iteration counts, so it could be named something likeiterations.
maskis a Boolean array selecting pixels that have not yet diverged to infinity, so something likenot_divergedorlivewould convey this better.
On each iteration, the iteration counts of the escaped pixels are incremented. This means that some pixels get incremented many times, for example a pixel that escapes on the first iteration gets its count incremented 256 times. It would be more efficient to set the iteration count for each pixel just once. A convenient time to do this is when it escapes.
As the number of iterations goes up, the number of pixels that have not escaped to infinity gets smaller and smaller. But the masking operations are always on the whole array. It would be more efficient to keep track of the indexes of the pixels that have not escaped, so that subsequent operations are on smaller and smaller arrays.
2. Revised code
im, re = np.ogrid[min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
z = (re + 1j * im).flatten()
live, = np.indices(z.shape) # indexes of pixels that have not escaped
iterations = np.empty_like(z, dtype=int)
for i in range(iterations_count):
z_live = z[live] = mapping(z[live])
escaped = abs(z_live) > threshold
iterations[live[escaped]] = i
live = live[~escaped]
iterations[live] = iterations_count - 1
return (iterations_count - iterations).reshape((height, width))
Notes
This is about three times as fast as the code in the post.
Because we are maintaining an array of indexes, it is convenient to flatten the
zarray and then reshapeiterationsto two dimensions before returning it. If we left the array two-dimensional, there would need to be two arrays of indexes,live_iandlive_j.Pixels that don't escape are given the value
iterations_count - 1in order to match the code in the post. It would make more sense to useiterations_countor a larger value here.The subtraction
iterations_count - iterationsis only there so that the returned values match the code in the post. The subtraction could be omitted if you reverse the colour map.
add a comment |
1. Review
Some of the variable names could be improved:
complex_planeis an array of $z$ values for each pixel in the image, so naming itzwould help the reader relate it to thezindouady_hubbard_polynomial.
imaginary_axisandreal_axisare only used once in the very next line, so there is no need for them to have long and memorable names. I would use something short likeimandre.
resultis an array of iteration counts, so it could be named something likeiterations.
maskis a Boolean array selecting pixels that have not yet diverged to infinity, so something likenot_divergedorlivewould convey this better.
On each iteration, the iteration counts of the escaped pixels are incremented. This means that some pixels get incremented many times, for example a pixel that escapes on the first iteration gets its count incremented 256 times. It would be more efficient to set the iteration count for each pixel just once. A convenient time to do this is when it escapes.
As the number of iterations goes up, the number of pixels that have not escaped to infinity gets smaller and smaller. But the masking operations are always on the whole array. It would be more efficient to keep track of the indexes of the pixels that have not escaped, so that subsequent operations are on smaller and smaller arrays.
2. Revised code
im, re = np.ogrid[min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
z = (re + 1j * im).flatten()
live, = np.indices(z.shape) # indexes of pixels that have not escaped
iterations = np.empty_like(z, dtype=int)
for i in range(iterations_count):
z_live = z[live] = mapping(z[live])
escaped = abs(z_live) > threshold
iterations[live[escaped]] = i
live = live[~escaped]
iterations[live] = iterations_count - 1
return (iterations_count - iterations).reshape((height, width))
Notes
This is about three times as fast as the code in the post.
Because we are maintaining an array of indexes, it is convenient to flatten the
zarray and then reshapeiterationsto two dimensions before returning it. If we left the array two-dimensional, there would need to be two arrays of indexes,live_iandlive_j.Pixels that don't escape are given the value
iterations_count - 1in order to match the code in the post. It would make more sense to useiterations_countor a larger value here.The subtraction
iterations_count - iterationsis only there so that the returned values match the code in the post. The subtraction could be omitted if you reverse the colour map.
1. Review
Some of the variable names could be improved:
complex_planeis an array of $z$ values for each pixel in the image, so naming itzwould help the reader relate it to thezindouady_hubbard_polynomial.
imaginary_axisandreal_axisare only used once in the very next line, so there is no need for them to have long and memorable names. I would use something short likeimandre.
resultis an array of iteration counts, so it could be named something likeiterations.
maskis a Boolean array selecting pixels that have not yet diverged to infinity, so something likenot_divergedorlivewould convey this better.
On each iteration, the iteration counts of the escaped pixels are incremented. This means that some pixels get incremented many times, for example a pixel that escapes on the first iteration gets its count incremented 256 times. It would be more efficient to set the iteration count for each pixel just once. A convenient time to do this is when it escapes.
As the number of iterations goes up, the number of pixels that have not escaped to infinity gets smaller and smaller. But the masking operations are always on the whole array. It would be more efficient to keep track of the indexes of the pixels that have not escaped, so that subsequent operations are on smaller and smaller arrays.
2. Revised code
im, re = np.ogrid[min_coordinate.imag: max_coordinate.imag: height * 1j,
min_coordinate.real: max_coordinate.real: width * 1j]
z = (re + 1j * im).flatten()
live, = np.indices(z.shape) # indexes of pixels that have not escaped
iterations = np.empty_like(z, dtype=int)
for i in range(iterations_count):
z_live = z[live] = mapping(z[live])
escaped = abs(z_live) > threshold
iterations[live[escaped]] = i
live = live[~escaped]
iterations[live] = iterations_count - 1
return (iterations_count - iterations).reshape((height, width))
Notes
This is about three times as fast as the code in the post.
Because we are maintaining an array of indexes, it is convenient to flatten the
zarray and then reshapeiterationsto two dimensions before returning it. If we left the array two-dimensional, there would need to be two arrays of indexes,live_iandlive_j.Pixels that don't escape are given the value
iterations_count - 1in order to match the code in the post. It would make more sense to useiterations_countor a larger value here.The subtraction
iterations_count - iterationsis only there so that the returned values match the code in the post. The subtraction could be omitted if you reverse the colour map.
answered Dec 27 '18 at 19:00
Gareth ReesGareth Rees
45.3k3100182
45.3k3100182
add a comment |
add a comment |
For douady_hubbard_polynomial, you're missing a return type.
This:
for _ in itertools.repeat(None, iterations_count):
can just be
for _ in range(iterations_count):
I don't see any other obvious issues.
3
itertools.repeat(None, iterations_count)is much faster for bigints since it doesn't require creating redundant objects, you can look at this answer
– Azat Ibrakov
Dec 24 '18 at 15:37
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
1
I did some timings, and it looks likeitertools.repeatwon't be of any help due to breaking out of a loop when themaskbecomes empty, so it won't reach bigints where the difference will be significant. I'm gonna change it torange.
– Georgy
Dec 24 '18 at 16:22
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
add a comment |
For douady_hubbard_polynomial, you're missing a return type.
This:
for _ in itertools.repeat(None, iterations_count):
can just be
for _ in range(iterations_count):
I don't see any other obvious issues.
3
itertools.repeat(None, iterations_count)is much faster for bigints since it doesn't require creating redundant objects, you can look at this answer
– Azat Ibrakov
Dec 24 '18 at 15:37
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
1
I did some timings, and it looks likeitertools.repeatwon't be of any help due to breaking out of a loop when themaskbecomes empty, so it won't reach bigints where the difference will be significant. I'm gonna change it torange.
– Georgy
Dec 24 '18 at 16:22
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
add a comment |
For douady_hubbard_polynomial, you're missing a return type.
This:
for _ in itertools.repeat(None, iterations_count):
can just be
for _ in range(iterations_count):
I don't see any other obvious issues.
For douady_hubbard_polynomial, you're missing a return type.
This:
for _ in itertools.repeat(None, iterations_count):
can just be
for _ in range(iterations_count):
I don't see any other obvious issues.
edited Dec 24 '18 at 17:58
answered Dec 24 '18 at 14:58
ReinderienReinderien
3,982821
3,982821
3
itertools.repeat(None, iterations_count)is much faster for bigints since it doesn't require creating redundant objects, you can look at this answer
– Azat Ibrakov
Dec 24 '18 at 15:37
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
1
I did some timings, and it looks likeitertools.repeatwon't be of any help due to breaking out of a loop when themaskbecomes empty, so it won't reach bigints where the difference will be significant. I'm gonna change it torange.
– Georgy
Dec 24 '18 at 16:22
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
add a comment |
3
itertools.repeat(None, iterations_count)is much faster for bigints since it doesn't require creating redundant objects, you can look at this answer
– Azat Ibrakov
Dec 24 '18 at 15:37
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
1
I did some timings, and it looks likeitertools.repeatwon't be of any help due to breaking out of a loop when themaskbecomes empty, so it won't reach bigints where the difference will be significant. I'm gonna change it torange.
– Georgy
Dec 24 '18 at 16:22
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
3
3
itertools.repeat(None, iterations_count) is much faster for big ints since it doesn't require creating redundant objects, you can look at this answer– Azat Ibrakov
Dec 24 '18 at 15:37
itertools.repeat(None, iterations_count) is much faster for big ints since it doesn't require creating redundant objects, you can look at this answer– Azat Ibrakov
Dec 24 '18 at 15:37
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
That's pretty cool; I didn't know that. I'd still suggest that it's premature optimization and highly unlikely to be a bottleneck of any significance.
– Reinderien
Dec 24 '18 at 15:44
1
1
I did some timings, and it looks like
itertools.repeat won't be of any help due to breaking out of a loop when the mask becomes empty, so it won't reach big ints where the difference will be significant. I'm gonna change it to range.– Georgy
Dec 24 '18 at 16:22
I did some timings, and it looks like
itertools.repeat won't be of any help due to breaking out of a loop when the mask becomes empty, so it won't reach big ints where the difference will be significant. I'm gonna change it to range.– Georgy
Dec 24 '18 at 16:22
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
About using the asterisk. I think it is a good practice to force a user to use keyword arguments. When you said about arbitrary arguments, I think, you referred to another thing which is not my case.
– Georgy
Dec 24 '18 at 16:35
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
@Georgy I removed my asterisk feedback from the answer. That said, this is a style decision, and one I disagree with. It should be left to the caller to determine whether adding explicit kwarg names makes the call more clear, or whether the parameters being passed are obvious and the code can be made more terse.
– Reinderien
Dec 24 '18 at 18:01
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210271%2fgenerating-julia-set%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown