Generate uniformly distributed points in a circle - python

'''
I am trying to generate points in a circle that should be uniformly distributed but I get a weird pattern. If I increase the radius R to a very large value, the distribution appears normal but with smaller R values it generates spirals. Any suggestions to improve the code?
'''
from numpy.random import uniform
#from scipy.stats import uniform
import matplotlib.pyplot as plt
import numpy as np
import math
R = 5
# Generate uniformly distributed random numbers.
rand_num = []
for i in range(30000):
rand_num.append(np.random.uniform(0,1))
# Use these generated numbers to obtain the CDF of the radius which is the true radius i.e. r = R*sqrt(random()).
radius = []
for n,data in enumerate(rand_num):
radius.append(R*math.sqrt(data))
# Generate the angle using the same uniformly distributed random numbers.
theta2 = []
for n, k in enumerate(radius):
theta2.append(2*math.pi*radius[n])
# Calculate the corresponding x-coordinate.
x = []
for j,v in enumerate(radius):
x.append(radius[j]*math.cos(theta2[j]))
x = np.array(x)
# Calculate the correspoding y-coordinate.
y = []
for j,v in enumerate(radius):
y.append(radius[j]*math.sin(theta2[j]))
y = np.array(y)
# Finally plot the coordinates.
plt.figure(figsize=(10,10))
plt.scatter(x, y, marker='o')

Yes, the code should give you a spiral since both theta and radius are proportional to rand_num. Instead, you should generate theta and radius independently. Also, use numpy's vectorized operators instead of math's
R = 5
num_points = 10000
np.random.seed(1)
theta = np.random.uniform(0,2*np.pi, num_points)
radius = np.random.uniform(0,R, num_points) ** 0.5
x = radius * np.cos(theta)
y = radius * np.sin(theta)
# visualize the points:
plt.scatter(x,y, s=1)
Output:

Related

Creating a structured non-rectangular mesh in python

I am trying to model the temperature distribution in a rocket nozzle, I am doing this with the heat equation solved using finite difference method. In order to apply this method I need my mesh to fit the geometry of my modelled rocket engine. I have trouble doing this as the meshgrid function seems to only work for rectangular mesh's.
I tried using the code bellow, and want the mesh to only go up to the red line, but cannot find a way of doing this.
import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits import mplot3d
# Grid spacing size
Dx = 0.1
L = 10
L_T = 2
D_T = 1
D_C = 2
D_E = 5
maxtime = 10
# forming the x grid spacing
x = np.arange(0,L,Dx)
y = np.zeros(len(x))
# factor to scale loop so have integers
m = 10
# gradient of the engine line
M = (D_C-D_T)/(L_T)
#inital height of engine is at combustion point.
y[0] = D_C
for i in range(1,int(L_T*m),int(Dx*m)):
y[i] = y[i-1] - M*Dx
#Y[i] = np.arange(0,y[i])
for i in range(int(L_T*m),int(L*m),int(Dx*m)):
y[i] = y[i-1] + M*Dx
# set meshgrids
Xg , Yg = np.meshgrid(x,y)
pl.scatter(Xg,Yg)
pl.plot(x,y,c='red')

How can I calculate arbitrary values from a spline created with scipy.interpolate.Rbf?

I have several data points in 3 dimensional space (x, y, z) and have interpolated them using scipy.interpolate.Rbf. This gives me a spline nicely representing the surface of my 3D object. I would now like to determine several x and y pairs that have the same, arbitrary z value. I would like to do that in order to compute the cross section of my 3D object at any given value of z. Does someone know how to do that? Maybe there is also a better way to do that instead of using scipy.interpolate.Rbf.
Up to now I have evaluated the cross sections by making a contour plot using matplotlib.pyplot and extracting the displayed segments. 3D points and interpolated spline
segments extracted using a contour plot
I was able to solve the problem. I have calculated the area by triangulating the x-y data and cutting the triangles with the z-plane I wanted to calculate the cross-sectional area of (z=z0). Specifically, I have searched for those triangles whose z-values are both above and below z0. Then I have calculated the x and y values of the sides of these triangles where the sides are equal to z0. Then I use scipy.spatial.ConvexHull to sort the intersected points. Using the shoelace formula I can then determine the area.
I have attached the example code here:
import numpy as np
from scipy import spatial
import matplotlib.pyplot as plt
# Generation of random test data
n = 500
x = np.random.random(n)
y = np.random.random(n)
z = np.exp(-2*(x-.5)**2-4*(y-.5)**2)
z0 = .75
# Triangulation of the test data
triang= spatial.Delaunay(np.array([x, y]).T)
# Determine all triangles where not all points are above or below z0, i.e. the triangles that intersect z0
tri_inter=np.zeros_like(triang.simplices, dtype=np.int) # The triangles which intersect the plane at z0, filled below
i = 0
for tri in triang.simplices:
if ~np.all(z[tri] > z0) and ~np.all(z[tri] < z0):
tri_inter[i,:] = tri
i += 1
tri_inter = tri_inter[~np.all(tri_inter==0, axis=1)] # Remove all rows with only 0
# The number of interpolated values for x and y has twice the length of the triangles
# Because each triangle intersects the plane at z0 twice
x_inter = np.zeros(tri_inter.shape[0]*2)
y_inter = np.zeros(tri_inter.shape[0]*2)
for j, tri in enumerate(tri_inter):
# Determine which of the three points are above and which are below z0
points_above = []
points_below = []
for i in tri:
if z[i] > z0:
points_above.append(i)
else:
points_below.append(i)
# Calculate the intersections and put the values into x_inter and y_inter
t = (z0-z[points_below[0]])/(z[points_above[0]]-z[points_below[0]])
x_new = t * (x[points_above[0]]-x[points_below[0]]) + x[points_below[0]]
y_new = t * (y[points_above[0]]-y[points_below[0]]) + y[points_below[0]]
x_inter[j*2] = x_new
y_inter[j*2] = y_new
if len(points_above) > len(points_below):
t = (z0-z[points_below[0]])/(z[points_above[1]]-z[points_below[0]])
x_new = t * (x[points_above[1]]-x[points_below[0]]) + x[points_below[0]]
y_new = t * (y[points_above[1]]-y[points_below[0]]) + y[points_below[0]]
else:
t = (z0-z[points_below[1]])/(z[points_above[0]]-z[points_below[1]])
x_new = t * (x[points_above[0]]-x[points_below[1]]) + x[points_below[1]]
y_new = t * (y[points_above[0]]-y[points_below[1]]) + y[points_below[1]]
x_inter[j*2+1] = x_new
y_inter[j*2+1] = y_new
# sort points to calculate area
hull = spatial.ConvexHull(np.array([x_inter, y_inter]).T)
x_hull, y_hull = x_inter[hull.vertices], y_inter[hull.vertices]
# Calculation of are using the shoelace formula
area = 0.5*np.abs(np.dot(x_hull,np.roll(y_hull,1))-np.dot(y_hull,np.roll(x_hull,1)))
print('Area:', area)
plt.figure()
plt.plot(x_inter, y_inter, 'ro')
plt.plot(x_hull, y_hull, 'b--')
plt.triplot(x, y, triangles=tri_inter, color='k')
plt.show()

Adding random weighted point

Let's say I have a blank canvas with 2 red points in it.
Is there an algorithm to randomly add a point in the canvas but in a way where it's more bias to the red points with a supplied radius?
Here's a crude image as an example:
Even though this question is for Python it really applies for any language.
Sure. Select first point or second point randomly, then generate some distribution with single scale parameter in polar coordinates, then shift by
center point position. Select some reasonable radial distribution (gaussian in the code below, exponential or Cauchy might work as well)
import math
import random
import matplotlib.pyplot as plt
def select_point():
p = random.random()
if p < 0.5:
return 0
return 1
def sample_point(R):
"""
Sample point inpolar coordinates
"""
phi = 2.0 * math.pi * random.random() # angle
r = R * random.gauss(0.0, 1.0) # might try different radial distribution, R*random.expovariate(1.0)
return (r * math.cos(phi), r * math.sin(phi))
def sample(R, points):
idx = select_point()
x, y = sample_point(R)
return (x + points[idx][0], y + points[idx][1])
R = 1.0
points = [(7.1, 3.3), (4.8, -1.4)]
random.seed(12345)
xx = []
yy = []
cc = []
xx.append(points[0][0])
xx.append(points[1][0])
yy.append(points[0][1])
yy.append(points[1][1])
cc.append(0.8)
cc.append(0.8)
for k in range(0, 50):
x, y = sample(R, points)
xx.append(x)
yy.append(y)
cc.append(0.3)
plt.scatter(xx, yy, c=cc)
plt.show()
Picture

How to rotate a cylinder without causing a 'sheared' appearance

I have plotted a 'tear drop' shaped cylinder in matplotlib. To obtain the tear drop shape I plotted a normal cylinder from theta = 0 to theta = pi and an ellipse from theta = pi to theta = 2pi. However I am now trying to 'spin' the cylinder around it's axis which here is given conveniently by the z-axis.
I tried using the rotation matrix for rotating around the z-axis which Wikipedia gives as:
However when I try to rotate through -pi/3 radians, the cylinder becomes very disfigured.
Is there anyway to prevent this from happening?
Here is my code:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sin, cos, pi
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(0,2*pi, 1200)
Z = np.linspace(0,5,1000+600)
Z,theta = np.meshgrid(Z, theta)
X = []
Y = []
R = 0.003
#calculate the x and y values
for i in theta:
cnt = 0
tempX = []
tempY = []
for j in i:
#circle
if(i[0]<=pi):
tempX.append(R*cos(j))
tempY.append(R*sin(j))
cnt+=1
#ellipse
else:
tempX.append(R*cos(j))
tempY.append(0.006*sin(j))
X.append(tempX)
Y.append(tempY)
X1 = np.array(X)
Y1 = np.array(Y)
#rotate around the Z axis
a = -pi/3
for i in range(len(X)):
X1[i] = cos(a)*X1[i]-sin(a)*Y1[i]
Y1[i] = sin(a)*X1[i]+cos(a)*Y1[i]
#plot
ax.plot_surface(X1,Y1,Z,linewidth = 0, shade = True, alpha = 0.3)
ax.set_xlim(-0.01,0.01)
ax.set_ylim(-0.01, 0.01)
azimuth = 173
elevation = 52
ax.view_init(elevation, azimuth)
plt.show()
Your rotating is flawed: To calculate Y1[i] you need the old value of X1[i], but you already updated it. You can try something like
X1[i], Y1[i] = cos(a)*X1[i]-sin(a)*Y1[i], sin(a)*X1[i]+cos(a)*Y1[i]
if you want to make the matrix multiplication a bit more obvious (and fix the bug) you could also do the following (please doublecheck that the matrix is correct and that the multiplication is in the right order, I did not test this):
rotation_matrix = np.array([[cos(a), -sin(a)], [sin(a), cos(a)]])
x, y = zip(*[(x,y) # rotation_matrix for x,y in zip(x,y)])
the # is new in 3.5 and for numpy array it's defined to be the matrix multiplication. If you are on a version below 3.5 you can use np.dot.
The zip(*...) is necessary to get a pair of lists instead of a list of pairs. See also this answer

Method to uniformly randomly populate a disk with points in python

I have an application that requires a disk populated with 'n' points in a quasi-random fashion. I want the points to be somewhat random, but still have a more or less regular density over the disk.
My current method is to place a point, check if it's inside the disk, and then check if it is also far enough away from all other points already kept. My code is below:
import os
import random
import math
# ------------------------------------------------ #
# geometric constants
center_x = -1188.2
center_y = -576.9
center_z = -3638.3
disk_distance = 2.0*5465.6
disk_diam = 5465.6
# ------------------------------------------------ #
pts_per_disk = 256
closeness_criteria = 200.0
min_closeness_criteria = disk_diam/closeness_criteria
disk_center = [(center_x-disk_distance),center_y,center_z]
pts_in_disk = []
while len(pts_in_disk) < (pts_per_disk):
potential_pt_x = disk_center[0]
potential_pt_dy = random.uniform(-disk_diam/2.0, disk_diam/2.0)
potential_pt_y = disk_center[1]+potential_pt_dy
potential_pt_dz = random.uniform(-disk_diam/2.0, disk_diam/2.0)
potential_pt_z = disk_center[2]+potential_pt_dz
potential_pt_rad = math.sqrt((potential_pt_dy)**2+(potential_pt_dz)**2)
if potential_pt_rad < (disk_diam/2.0):
far_enough_away = True
for pt in pts_in_disk:
if math.sqrt((potential_pt_x - pt[0])**2+(potential_pt_y - pt[1])**2+(potential_pt_z - pt[2])**2) > min_closeness_criteria:
pass
else:
far_enough_away = False
break
if far_enough_away:
pts_in_disk.append([potential_pt_x,potential_pt_y,potential_pt_z])
outfile_name = "pt_locs_x_lo_"+str(pts_per_disk)+"_pts.txt"
outfile = open(outfile_name,'w')
for pt in pts_in_disk:
outfile.write(" ".join([("%.5f" % (pt[0]/1000.0)),("%.5f" % (pt[1]/1000.0)),("%.5f" % (pt[2]/1000.0))])+'\n')
outfile.close()
In order to get the most even point density, what I do is basically iteratively run this script using another script, with the 'closeness' criteria reduced for each successive iteration. At some point, the script can not finish, and I just use the points of the last successful iteration.
So my question is rather broad: is there a better way to do this? My method is ok for now, but my gut says that there is a better way to generate such a field of points.
An illustration of the output is graphed below, one with a high closeness criteria, and another with a 'lowest found' closeness criteria (what I want).
A simple solution based on Disk Point Picking from MathWorld:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
r = np.random.uniform(low=0, high=1, size=n) # radius
theta = np.random.uniform(low=0, high=2*np.pi, size=n) # angle
x = np.sqrt(r) * np.cos(theta)
y = np.sqrt(r) * np.sin(theta)
# for plotting circle line:
a = np.linspace(0, 2*np.pi, 500)
cx,cy = np.cos(a), np.sin(a)
fg, ax = plt.subplots(1, 1)
ax.plot(cx, cy,'-', alpha=.5) # draw unit circle line
ax.plot(x, y, '.') # plot random points
ax.axis('equal')
ax.grid(True)
fg.canvas.draw()
plt.show()
It gives.
Alternatively, you also could create a regular grid and distort it randomly:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri
n = 20
tt = np.linspace(-1, 1, n)
xx, yy = np.meshgrid(tt, tt) # create unit square grid
s_x, s_y = xx.ravel(), yy.ravel()
ii = np.argwhere(s_x**2 + s_y**2 <= 1).ravel() # mask off unwanted points
x, y = s_x[ii], s_y[ii]
triang = tri.Triangulation(x, y) # create triangluar grid
# distort the grid
g = .5 # distortion factor
rx = x + np.random.uniform(low=-g/n, high=g/n, size=x.shape)
ry = y + np.random.uniform(low=-g/n, high=g/n, size=y.shape)
rtri = tri.Triangulation(rx, ry, triang.triangles) # distorted grid
# for circle:
a = np.linspace(0, 2*np.pi, 500)
cx,cy = np.cos(a), np.sin(a)
fg, ax = plt.subplots(1, 1)
ax.plot(cx, cy,'k-', alpha=.2) # circle line
ax.triplot(triang, "g-", alpha=.4)
ax.triplot(rtri, 'b-', alpha=.5)
ax.axis('equal')
ax.grid(True)
fg.canvas.draw()
plt.show()
It gives
The triangles are just there for visualization. The obvious disadvantage is that depending on your choice of grid, either in the middle or on the borders (as shown here), there will be more or less large "holes" due to the grid discretization.
If you have a defined area like a disc (circle) that you wish to generate random points within you are better off using an equation for a circle and limiting on the radius:
x^2 + y^2 = r^2 (0 < r < R)
or parametrized to two variables
cos(a) = x/r
sin(a) = y/r
sin^2(a) + cos^2(a) = 1
To generate something like the pseudo-random distribution with low density you should take the following approach:
For randomly distributed ranges of r and a choose n points.
This allows you to generate your distribution to roughly meet your density criteria.
To understand why this works imagine your circle first divided into small rings of length dr, now imagine your circle divided into pie slices of angle da. Your randomness now has equal probability over the whole boxed area arou d the circle. If you divide the areas of allowed randomness throughout your circle you will get a more even distribution around the overall circle and small random variation for the individual areas giving you the psudo-random look and feel you are after.
Now your job is just to generate n points for each given area. You will want to have n be dependant on r as the area of each division changes as you move out of the circle. You can proportion this to the exact change in area each space brings:
for the n-th to n+1-th ring:
d(Area,n,n-1) = Area(n) - Area(n-1)
The area of any given ring is:
Area = pi*(dr*n)^2 - pi*(dr*(n-1))
So the difference becomes:
d(Area,n,n-1) = [pi*(dr*n)^2 - pi*(dr*(n-1))^2] - [pi*(dr*(n-1))^2 - pi*(dr*(n-2))^2]
d(Area,n,n-1) = pi*[(dr*n)^2 - 2*(dr*(n-1))^2 + (dr*(n-2))^2]
You could expound this to gain some insight on how much n should increase but it may be faster to just guess at some percentage increase (30%) or something.
The example I have provided is a small subset and decreasing da and dr will dramatically improve your results.
Here is some rough code for generating such points:
import random
import math
R = 10.
n_rings = 10.
n_angles = 10.
dr = 10./n_rings
da = 2*math.pi/n_angles
base_points_per_division = 3
increase_per_level = 1.1
points = []
ring = 0
while ring < n_rings:
angle = 0
while angle < n_angles:
for i in xrange(int(base_points_per_division)):
ra = angle*da + da*math.random()
rr = r*dr + dr*random.random()
x = rr*math.cos(ra)
y = rr*math.sin(ra)
points.append((x,y))
angle += 1
base_points_per_division = base_points_per_division*increase_per_level
ring += 1
I tested it with the parameters:
n_rings = 20
n_angles = 20
base_points = .9
increase_per_level = 1.1
And got the following results:
It looks more dense than your provided image, but I imagine further tweaking of those variables could be beneficial.
You can add an additional part to scale the density properly by calculating the number of points per ring.
points_per_ring = densitymath.pi(dr**2)*(2*n+1)
points_per_division = points_per_ring/n_angles
This will provide a an even better scaled distribution.
density = .03
points = []
ring = 0
while ring < n_rings:
angle = 0
base_points_per_division = density*math.pi*(dr**2)*(2*ring+1)/n_angles
while angle < n_angles:
for i in xrange(int(base_points_per_division)):
ra = angle*da + min(da,da*random.random())
rr = ring*dr + dr*random.random()
x = rr*math.cos(ra)
y = rr*math.sin(ra)
points.append((x,y))
angle += 1
ring += 1
Giving better results using the following parameters
R = 1.
n_rings = 10.
n_angles = 10.
density = 10/(dr*da) # ~ ten points per unit area
With a graph...
and for fun you can graph the divisions to see how well it is matching your distriubtion and adjust.
Depending on how random the points need to be, it may be simple enough to just make a grid of points within the disk, and then displace each point by some small but random amount.
It may be that you want more randomness, but if you just want to fill your disc with an even-looking distribution of points that aren't on an obvious grid, you could try a spiral with a random phase.
import math
import random
import pylab
n = 300
alpha = math.pi * (3 - math.sqrt(5)) # the "golden angle"
phase = random.random() * 2 * math.pi
points = []
for k in xrange(n):
theta = k * alpha + phase
r = math.sqrt(float(k)/n)
points.append((r * math.cos(theta), r * math.sin(theta)))
pylab.scatter(*zip(*points))
pylab.show()
Probability theory ensures that the rejection method is an appropriate method
to generate uniformly distributed points within the disk, D(0,r), centered at origin and of radius r. Namely, one generates points within the square [-r,r] x [-r,r], until a point falls within the disk:
do{
generate P in [-r,r]x[-r,r];
}while(P[0]**2+P[1]**2>r);
return P;
unif_rnd_disk is a generator function implementing this rejection method:
import matplotlib.pyplot as plt
import numpy as np
import itertools
def unif_rnd_disk(r=1.0):
pt=np.zeros(2)
while True:
yield pt
while True:
pt=-r+2*r*np.random.random(2)
if (pt[0]**2+pt[1]**2<=r):
break
G=unif_rnd_disk()# generator of points in disk D(0,r=1)
X,Y=zip(*[pt for pt in itertools.islice(G, 1, 1000)])
plt.scatter(X, Y, color='r', s=3)
plt.axis('equal')
If we want to generate points in a disk centered at C(a,b), we have to apply a translation to the points in the disk D(0,r):
C=[2.0, -3.5]
plt.scatter(C[0]+np.array(X), C[1]+np.array(Y), color='r', s=3)
plt.axis('equal')

Categories

Resources