displaying Mandelbrot set in python using matplotlib.pyplot and numpy - python

I am trying to get a plot of a Mandelbrot set and having trouble plotting the expected plot.
As I understand, the Mandelbrot set is made up of values c, which would converge if are iterated through the following equation z = z**2 + c. I used the initial value of z = 0.
Initially, I was getting a straight line. I look for solutions online to see where I went wrong. Using the following link in particular, I attempted to improve my code:
https://scipy-lectures.org/intro/numpy/auto_examples/plot_mandelbrot.html
Here is my improved code. I don't really understand the reason of using np.newaxis and why I am plotting the final z values that converge. Am I misunderstanding the definition of the Mandelbrot set?
# initial values
loop = 50 # number of interations
div = 600 # divisions
# all possible values of c
c = np.linspace(-2,2,div)[:,np.newaxis] + 1j*np.linspace(-2,2,div)[np.newaxis,:]
z = 0
for n in range(0,loop):
z = z**2 + c
plt.rcParams['figure.figsize'] = [12, 7.5]
z = z[abs(z) < 2] # removing z values that diverge
plt.scatter(z.real, z.imag, color = "black" ) # plotting points
plt.xlabel("Real")
plt.ylabel("i (imaginary)")
plt.xlim(-2,2)
plt.ylim(-1.5,1.5)
plt.savefig("plot.png")
plt.show()
and got the following image, which looks closer to the Mandelbrot set than anything I got so far. But it looks more of a starfish with scattered dots around it.
Image
For reference, here is my initial code before improvement:
# initial values
loop = 50
div = 50
clist = np.linspace(-2,2,div) + 1j*np.linspace(-1.5,1.5,div) # range of c values
all_results = []
for c in clist: # for each value of c
z = 0 # starting point
for a in range(0,loop):
negative = 0 # unstable
z = z**2 + c
if np.abs(z) > 2:
negative +=1
if negative > 2:
break
if negative == 0:
all_results.append([c,"blue"]) #converging
else:
all_results.append([c,"black"]) # not converging

Alternatively, with another small change to the code in the question, one can use the values of z to colorize the plot. One can store the value of n where the absolute value of the series becomes larger than 2 (meaning it diverges), and color the points outside the Mandelbrot set with it:
import pylab as plt
import numpy as np
# initial values
loop = 50 # number of interations
div = 600 # divisions
# all possible values of c
c = np.linspace(-2,2,div)[:,np.newaxis] + 1j*np.linspace(-2,2,div)[np.newaxis,:]
# array of ones of same dimensions as c
ones = np.ones(np.shape(c), np.int)
# Array that will hold colors for plot, initial value set here will be
# the color of the points in the mandelbrot set, i.e. where the series
# converges.
# For the code below to work, this initial value must at least be 'loop'.
# Here it is loop + 5
color = ones * loop + 5
z = 0
for n in range(0,loop):
z = z**2 + c
diverged = np.abs(z)>2
# Store value of n at which series was detected to diverge.
# The later the series is detected to diverge, the higher
# the 'color' value.
color[diverged] = np.minimum(color[diverged], ones[diverged]*n)
plt.rcParams['figure.figsize'] = [12, 7.5]
# contour plot with real and imaginary parts of c as axes
# and colored according to 'color'
plt.contourf(c.real, c.imag, color)
plt.xlabel("Real($c$)")
plt.ylabel("Imag($c$)")
plt.xlim(-2,2)
plt.ylim(-1.5,1.5)
plt.savefig("plot.png")
plt.show()

The plot doesn't look correct, because in the code in the question z (i.e. the iterated variable) is plotted. Iterating z = z*z + c, the Mandelbrot set is given by those real, imaginary part pairs of c, for which the series doesn't diverge. Hence the small change to the code as shown below gives the correct Mandelbrot plot:
import pylab as plt
import numpy as np
# initial values
loop = 50 # number of interations
div = 600 # divisions
# all possible values of c
c = np.linspace(-2,2,div)[:,np.newaxis] + 1j*np.linspace(-2,2,div)[np.newaxis,:]
z = 0
for n in range(0,loop):
z = z**2 + c
plt.rcParams['figure.figsize'] = [12, 7.5]
p = c[abs(z) < 2] # removing c values for which z has diverged
plt.scatter(p.real, p.imag, color = "black" ) # plotting points
plt.xlabel("Real")
plt.ylabel("i (imaginary)")
plt.xlim(-2,2)
plt.ylim(-1.5,1.5)
plt.savefig("plot.png")
plt.show()

Related

Efficient way to swap array elements that are flipped

Suppose you have an array of points that have a chance to be flipped symmetrically (shown below)
from matplotlib import pyplot as plt
import numpy as np
# Data
a = np.array([0.1,-0.325,-0.55,0.775,1]) # x-axis
b = np.array([10,-3.077,-1.818,1.2903,1]) # y-axis
c = np.array([-0.1,0.325,0.55,-0.775,-1]) # x-axis
d = np.array([-10,3.077,1.818,-1.2903,-1])# y-axis
y = [a,b,c,d] # The array is created this way intentionally for when I apply it to my case
plt.plot(y[0],y[1],'k.')
plt.plot(y[2],y[3],'r.')
plt.show()
How do I automatically check each array elements and write a condition that corrects the position of these points, assuming that we know what form it is supposed to have?
edit:
This is the graph I am trying to get
For this example will work
a = np.absolute(a)
b = np.absolute(b)
c = -np.absolute(c)
d = -np.absolute(d)
but other situations may need minus for different lists. So it can be bigg problem to recognize which list need minus.
Better can be to create pairs (x,y) and split them to two list by x > 0 x < 0 (or y > 0 y < 0) and later convert pairs back to lists x and y
(maybe with numpy you could do it easier and faster)
all_pairs = list(zip(a,b)) + list(zip(c,d))
# ---
lower = []
higher = []
for pair in all_pairs:
if pair[0] > 0:
higher.append(pair)
else:
lower.append(pair)
# ---
a, b = list(zip(*higher))
c, d = list(zip(*lower))
Minimal working code
import numpy as np
import matplotlib.pyplot as plt
# Data
a = np.array([0.1,-0.325,-0.55,0.775,1]) # x-axis
b = np.array([10,-3.077,-1.818,1.2903,1]) # y-axis
c = np.array([-0.1,0.325,0.55,-0.775,-1]) # x-axis
d = np.array([-10,3.077,1.818,-1.2903,-1])# y-axis
all_pairs = list(zip(a,b)) + list(zip(c,d))
print(all_pairs)
higher = []
lower = []
for pair in all_pairs:
if pair[0] > 0:
higher.append(pair)
else:
lower.append(pair)
print(higher)
print(lower)
a, b = list(zip(*higher))
c, d = list(zip(*lower))
y = [a,b,c,d] # The array is created this way intentionally for when I apply it to my case
#plt.plot(y[0],y[1],'k.')
#plt.plot(y[2],y[3],'r.')
plt.plot(*y[0:2], 'k.')
plt.plot(*y[2:4], 'r.')
plt.show()

Regular Distribution of Points in the Volume of a Sphere

I'm trying to generate a regular n number of points within the volume of a sphere. I found this similar answer (https://scicomp.stackexchange.com/questions/29959/uniform-dots-distribution-in-a-sphere) on generating a uniform regular n number of points on the surface of a sphere, with the following code:
import numpy as np
n = 5000
r = 1
z = []
y = []
x = []
alpha = 4.0*np.pi*r*r/n
d = np.sqrt(alpha)
m_nu = int(np.round(np.pi/d))
d_nu = np.pi/m_nu
d_phi = alpha/d_nu
count = 0
for m in range (0,m_nu):
nu = np.pi*(m+0.5)/m_nu
m_phi = int(np.round(2*np.pi*np.sin(nu)/d_phi))
for n in range (0,m_phi):
phi = 2*np.pi*n/m_phi
xp = r*np.sin(nu)*np.cos(phi)
yp = r*np.sin(nu)*np.sin(phi)
zp = r*np.cos(nu)
x.append(xp)
y.append(yp)
z.append(zp)
count = count +1
which works as intended:
How can I modify this to generate a regular set of n points in the volume of a sphere?
Another method to do this, yielding uniformity in volume:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
dim_len = 30
spacing = 2 / dim_len
point_cloud = np.mgrid[-1:1:spacing, -1:1:spacing, -1:1:spacing].reshape(3, -1).T
point_radius = np.linalg.norm(point_cloud, axis=1)
sphere_radius = 0.5
in_points = point_radius < sphere_radius
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(point_cloud[in_points, 0], point_cloud[in_points, 1], point_cloud[in_points, 2], )
plt.show()
Output (matplotlib mixes up the view but it is a uniformly sampled sphere (in volume))
Uniform sampling, then checking if points are in the sphere or not by their radius.
Uniform sampling reference [see this answer's edit history for naiive sampling].
This method has the drawback of generating redundant points which are then discarded.
It has the upside of vectorization, which probably makes up for the drawback. I didn't check.
With fancy indexing, one could generate the same points as this method without generating redundant points, but I doubt it can be easily (or at all) vectorized.
Sample uniformly along X. For every value of X, you draw two Y from X²+Y²=1. Sample uniformly between these two Y. Then for every (X, Y) pair, you draw two Z from X²+Y²+Z²=1. Sample uniformly between these two Z.

Plotting In Python, Peicewise, Holoveiws, Bokeh, Heatmaps

I need to discriminate against non physical data in my heatmaps. I am using python(bokeh and holoviews).
Example Code:
import numpy as np
import holoviews as hv
import warnings\
warnings.filterwarnings("ignore")
hv.extension('bokeh')
%opts Image [colorbar=True tools=['hover']
%opts Image (cmap='rainbow')
%output max_frames=3000
import Definitions as def #these are my equations
a = 2
b = .2
c = .3
d = .4
e = 0
f = 0
g = 0
h = 0
i = .2
j = 0
l = .2
m = 1
N = 100 # number of points
yval = np.linspace(0.1,1,N)
xval = np.linspace(0,5,N)
bounds = (0,.1,5,1) #this sets the bounds from .1 to 1 on y axis and 0 to 5 on x axis
xval,yval = np.meshgrid(xval, yval, indexing 'xy')
v1val = def.v1(yval,b,a,m,l,xval) #Calling my these definitions from a seperate file
v2val = def.v2(b,m,a)
Zlist = def.Z(a,v2val/d,v2val/c,h,e,i,j,xval,l,v1val,f,g)
plot = hv.Image(np.flipud(Zlist), label = "Z Heat Map" \
,bounds = bounds, vdims = hv.Dimension('Z', range=(0,1))).redimlabel(x = 'x', y = 'y')
plot
This code makes a heat map where the value of the function Z is mapped as a color for a region of x and y. So Z depends on x and y and for different values of x and y, Z will have different colors.
My problem: I need to discriminate against any situations where v1val < c . The code I currently have plots all of the data but I need it to plot only the data for v1val > c and maybe assign a color like white or black to portion of the graph corresponding to v1val < c. I also similarly need to white out or blackout any region where v2val < d. Essentially I want to black out or white out regions of my heatmap that correspond to non physical data ie when v1val < c and when v2val < d.
I have been trying different things but each time I have some idea I get an error like " The truth of a value of an array with more than one element is ambiguous. Use a.any() or a.all()"
Help with blacking out this non physical data would be much appreciated.
You can see how to black out or white out regions of a heatmap in http://pyviz.org/tutorial/01_Workflow_Introduction.html :
def nansum(a, **kwargs):
return np.nan if np.isnan(a).all() else np.nansum(a, **kwargs)
heatmap = df.hvplot.heatmap('Year', 'State', 'measles', reduce_function=nansum)

Complete turn x angle values are mapped to half turn using asin function, how to mirror them back?

I have angles that form a complete turn in an array x, from -90 to 270 e.g. (it may be defined otherwise, like from 0 to 360 or -180 to 180) with step 1 or whatever.
asin function is valid only between -90 and +90.
Thus, angles < -90 or > 90 would be "mapped" between these values.
E.g. y = some_asin_func(over_sin(x)) will end up in an y value that is always between -90 and +90. So y is stuck between -90 and +90.
I do need to retrieve to which x-input is y related, because it's ambiguous yet: for example, the function over (x) will give the same y values for x = 120 and x = 60, or x = -47 and x = 223. Which is not what I want.
Put an other way; I need y making a complete turn as x does, ranging from where x starts up to where x ends.
An image will be better:
Here, x ranges between -90 (left) to 270 (right of the graph).
The valid part of the curve is between x=-90 and x=+90 (left half of the graph).
All other values are like mirrored about y=90 or y=-90.
For x=180 for example, I got y=0 and it should be y=180.
For x=270, I have y=-90 but it should be y=270, thus +360.
Here's a code sample:
A = 50 # you can make this value vary to have different curves like in the images, when A=0 -> shape is triangle-like, when A=90-> shape is square-like.
x = np.linspace(-90,270,int(1e3))
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
plt.plot(x,y)
plt.grid(True)
Once again, first left half of the graph is completely correct.
The right half is also correct in its behavior, but in final, here, it must be mirrored about an horizontal axis at position y=+90 when x>90, like this:
That is, it's like the function is mirrored about y=-90 and y=+90 for y where x is out of the range [-90,+90] and only where where x is out of the range [-90,+90].
I want to un-mirror it outside the valid [-90,+90] range:
about y=-90 where y is lower than -90
about y=+90 where y is greater than +90
And of course, modulo each complete turn.
Here an other example where x ranges from -180 to 180 and the desired behavior:
Yet:
Wanted:
I have first tested some simple thing up now:
A = 50
x = np.linspace(-180,180,int(1e3))
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
for i,j in np.ndenumerate(x):
xval = (j-180)%180-180
if (xval < -90):
y[i] = y[i]-val
elif (xval > 90):
y[i] = y[i]+val
plt.plot(x,y);
plt.grid(True)
plt.show()
which doesn't work at all but I think the background idea is there...
I guess it may be some kind of modulo trick but can't figure it out.
Here a solution that fixes the periodicity of the cos function 'brute force' by calculating an offset and a sign correction based on the x value. I'm sure there is something better out there, but I would almost need a drawing with the angles and distances involved.
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,1, figsize=(4,4))
x = np.linspace(-540,540,1000)
sign = np.sign(np.cos(np.pi*x/180))
offset = ((x-90)//180)*180
for A in range(1,91,9):
u = np.sin(np.pi*A/180)*np.cos(np.pi*x/180)
v = 180*(np.arcsin(u))/np.pi
y = 180*np.arcsin(np.sin(np.pi*x/180)/np.cos(np.pi*v/180))/np.pi
y = sign*y + offset
ax.plot(x,y)
ax.grid(True)
plt.show()
The result for the interval [-540, 540] looks like this:
Note that you can get pi also from numpy, so you don't need to import math -- I altered the code accordingly.
EDIT:
Apparently I first slightly misunderstood the OP's desired output. If the calculation of offset is just slightly changed, the result is as requested:
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,1, figsize=(4,4))
x = np.linspace(-720,720,1000)
sign = np.sign(np.cos(np.pi*x/180))
offset = ((x-90)//180 +1 )*180 - ((x-180)//360+1)*360
for A in range(1,91,9):
u = np.sin(np.pi*A/180)*np.cos(np.pi*x/180)
v = 180*(np.arcsin(u))/np.pi
y = 180*np.arcsin(np.sin(np.pi*x/180)/np.cos(np.pi*v/180))/np.pi
y = sign*y + offset
ax.plot(x,y)
ax.grid(True)
plt.show()
The result now looks like this:
Thank you #Thomas Kühn, it seems fine except I wanted to restrict the function in a single same turn in respect to y-values. Anyway, it's only aesthetics.
Here's what I found by my side. It's maybe not perfect but it works:
A = 50
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
for i,j in np.ndenumerate(x):
val = (j-180)%360-180
if (val < -90):
y[i] = -180-y[i]
elif (val > 90):
y[i] = 180-y[i]
Here are some expected results:
Range from -180 to +180
Range from 0 to +360
Range from -720 to +720
Range from -360 to +360 with some different A values.
Funny thing is that it reminds me some electronics diagrams as well.
Periodic phenomenons are everywhere!

pyplot hist() frequency histogram does not normalize to 1

I have a problem when using hist(). I created an array with 396 entries which contains every possible outcome of
X + 0.5 Y + Z,
where 0 <= X,Z <=5 and 0 <= Y <= 10. All I want to create is a frequency histogram whose bar heights coincide with the probability that a certain value is taken under the assumption of x,y,z being independently, uniformly distributed. I thought I'd give hist() a try:
import pandas as pd
import matplotlib.pyplot as plt
def activity(x,y,z):
return 1 * x + 0.5 * y + 1 * z
rangex = np.arange(6)
rangey = np.arange(11)
rangez = np.arange(6)
rhs = 10
activities = [activity(x,y,z) for x in rangex for y in rangey for z in rangez]
activities = pd.Series(activities)
fig, axes = plt.subplots(1,1)
n, bins, patches = axes.hist(activities, bins=np.linspace(-0.25, 15.25, num=32), \
normed=True)
Here is the weird thing: n sums up to 2!!!
With the choice of bins, I made sure that each item falls into exactly 1 bin, and I know that there are exactly 31 bins necessary, because my value range is from 0 through 15 in steps of 0.5.
I am sorry I couldn't simplify this example. A random trial with 100 values yielded a correct frequency. Instead, this is what the histogram looks like:
From the picture, it is obvious that the frequencies do not sum up to 1. There are, e.g., in the center 11 bars of height 0.1 or above. The frequencies of the red plot, however, do sum up to 1.
My question: Why do I get a false normalization?
See below the code to manually calculate a correct histogram:
barposs, barheight = zip(*activities.value_counts(normalize=True).iteritems())
plt.bar(np.array(barposs) - 0.25, np.array(barheight), width=0.5, color='red')
I appreciate any useful comment on that.

Categories