Related
I'm currently really stuck with some of my code and I can't seem to find the issue. Here is what I am trying to do:
I have a big outer circle in which I want to display smaller dots. These dots should be randomly distributed but should not overlap, thus they should have a minimum distance to each other.
What I have tried is to first randomly generate a point, check wether it is in the outer circle and if it is, append it to the final list of dot positions. Then another point is created, checked if in circle and then it should be checked if the dot has a minimum distance to the other dot(s) in the final list.
However, I seem to have some issues with my code as it will not run through whenever I set the required distances higher than 1. I have changed multiple things, but I cannot make it work.
Does anyone have an idea about what the problem might be?
Here's what I have been trying:
import random
import numpy as np
import math
#Variables
radiusOC = 57
size_obj = 7
required_dist = 5
no_stimuli = 3
def CreatePos(radiusOC, size_obj, required_dist, no_stimuli):
final_list = []
def GenRandPos(radiusOC,size_obj):
"""
Takes the radius of the outer circle and generates random dots within this radius. Then checks if the the dots are located
within the outer circle.
"""
while True:
xPos = random.randint(-radiusOC,radiusOC)
yPos = random.randint(-radiusOC,radiusOC)
# check if in Circle
on_circle = (xPos- 0)**2 + (yPos-0)**2
if (radiusOC-size_obj)**2 >= on_circle:
print("Still in circle",on_circle, xPos, yPos )
position = [xPos, yPos]
break
else:
print("Not in circle",on_circle, xPos, yPos )
continue
return position
def CheckSurrounding(position, final_list, required_dist):
"""
Takes dot positions that are in the visual field, the list of positions, and the distances dots are required to have from each other.
It is checked if there are dots close by or not.
"""
X1 = position[0]
Y1 = position[1]
dist_list = []
for elem in final_list:
for i in elem:
X2 = elem[0]
Y2 = elem[1]
dist = math.sqrt((X1-X2)**2 + (Y1-Y2)**2)
dist_list.append(dist)
if all(dist_list) >= required_dist:
return position
else:
return None
# append the first dot to the list
position = GenRandPos(radiusOC, size_obj)
final_list.append(position)
# now append the rest of the dots if they have a certain distance to each other
while len(final_list) < no_stimuli:
position = GenRandPos(radiusOC, size_obj)
if CheckSurrounding(position, final_list, required_dist) != None:
position = CheckSurrounding(position, final_list, required_dist)
final_list.append(position)
else:
continue
return final_list
ยดยดยด
In the line
if all(dist_list) >= required_dist:
all(dist_list) will be either True or False, which is numerically equivalent to either 1 or 0. If required_dist is greater than 1 the inequality will never be satisfied. I think that you intended this to be
if all(dist_list >= required_dist):
but this will not work since you cannot compare a list dist_list to a number required_dist. To fix it, convert dist_list to a numpy array:
if np.all(np.array(dist_list) >= required_dist):
By the way, the random points you are selecting will always have integer coordinates since you are using random.randint(), I am not sure if this is intentional.
The whole code can be made more efficient by using numpy arrays. For example:
import numpy as np
def CreatePos(radiusOC, size_obj, required_dist, no_stimuli):
final_list = []
def GenRandPos(radiusOC, size_obj):
"""
Takes the radius of the outer circle and generates
random dots within this radius. Then checks if the dots are
located within the outer circle.
"""
while True:
position = (2 * np.random.random(2) - 1) * radiusOC
# check if in Circle
if (radiusOC - size_obj)**2 >= (position**2).sum():
return position
def CheckSurrounding(position, final_list, required_dist):
"""
Takes dot positions that are in the visual field,
the list of positions, and the distances dots are
required to have from each other.
It is checked if there are dots close by or not.
"""
final_arr = np.array(final_list)
dist = ((np.array(final_list) - position)**2).sum(axis=1)
if np.all(np.array(dist) >= required_dist**2):
return position
# append the first dot to the list
position = GenRandPos(radiusOC, size_obj)
final_list.append(position)
# now append the rest of the dots if they have a certain distance to each other
while len(final_list) < no_stimuli:
position = GenRandPos(radiusOC, size_obj)
if CheckSurrounding(position, final_list, required_dist) is not None:
final_list.append(position)
return final_list
Note that this returns a list of points with coordinates given by floats, not integers.
Sample usage:
#Variables
radiusOC = 57
size_obj = 7
required_dist = 3
no_stimuli = 400
final_list = np.array(CreatePos(radiusOC, size_obj, required_dist, no_stimuli))
Plot the resulting points:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111)
ax.set_aspect("equal")
plt.scatter(f[:, 0], f[:, 1])
plt.show()
This gives:
I would add a condition in the final while loop so it can break if a new point cannot be found after some number of attempts. Otherwise, it may end up running indefinitely.
You need to figure out a way to estimate the max number of points given the distance. This can be extrapolated from the circle packing problem. https://planetcalc.com/7473/ I will comment if I have an easy check to do.
import numpy as np
from matplotlib import pyplot as plt
from scipy.spatial.distance import cdist
def random_in_circle(num_points=1000, R=1, min_dist=0.2):
assert min_dist < R, "Min distance between points must be smaller than the radius of outer circle"
assert R / (2 ** (num_points - 1)) < min_dist, "Min dist is too large"
points = []
while len(points) < num_points:
a = np.random.rand() * 2 * np.pi # random angle
r = R * np.random.rand() # random radius
point = r * np.array([np.cos(a), np.sin(a)])
if len(points) == 0:
points.append(point)
elif np.all(cdist([point], points) > min_dist):
points.append(point)
return np.vstack(points)
points = random_in_circle(num_points=1000, min_dist=0.01)
plt.scatter(points[:, 0], points[:, 1])
plt.show()
I'm trying to make a bifurcation diagram for the following iterated map:
x_n+1 = x_n * e^(r(1-x_n)).
First I defined the map:
def newmap(x,r):
return x*math.exp(r*(1-x))
Then I tried this:
def bifurcation_diagram(rmin=0, rmax=4, r_N=2000, N_min=4000, N = 1000):
rspace = np.linspace(rmin, rmax, r_N)
x = 0
rset = []
xset = []
for r in rspace:
for i in range(N_min + N):
x = newmap(x,r)
if i > N_min:
rset.append(r)
xset.append(x)
plt.figure(figsize=(16,7))
plt.xlim((rmin,rmax))
plt.ylim((0,5))
plt.scatter(rset,xset,s=0.3,c='C0',linewidth=0)
plt.xlabel(r'r', fontsize=20)
plt.ylabel(r'$x_{end}$', fontsize=29, rotation=0)
plt.show()
When I try bifurcation_diagram() I get a blank plot.
I'm not sure where I'm going wrong here.
The problem is that x=0 is a fixed point and so is x=1. If you switch x=0 to x=0.1 but otherwise leave it where it is, the first r value drives x to (what is for those values) the attracting fixed point 1. You need to put x=0.1 inside of the main loop:
for r in rspace:
x = 0.01
(with everything else as before).
I have a numpy array in python 2.7, which I am using the imshow() function to visualise. The code generating the array looks like:
from pylab import *
r0 = 3.0
S0 = 10.0
x = zeros((101,101))
noiseimg = zeros((101,101))
for i in range(101):
for j in range(101):
noiseimg[i,j] = noiseimg[i,j] + normal(3,1)
mean_i = randint(0,101)
mean_j = randint(0,101)
for i in range(101):
for j in range(101):
r = ((i-mean_i)**2 + (j-mean_j)**2)**0.5
x[i,j] = S0*(1+(r/r0)**2)**-1.5
x[i,j] = x[i,j] + noiseimg[i,j]
if (((i-50)**2 + (j-50)**2)**0.5 >= 40) and (((i-50)**2 + (j-50)**2)**0.5 <= 41):
x[i,j]=0
imshow(x)
show()
What this does is produce an image with a level of background noise, and one circularly symmetric source. There is a circle centred on the image, with a radius of 40 pixels.
What I need to know is how to find the location of the highest value pixel within that circle. I know how to find the maximum value in the circle, but not the [i,j] location of it.
Thank you!
My question has been flagged by stackoverflow as a potential duplicate, but this doesn't contain the location restrictions that I need.
One solution is to "zero" out all the elements surrounding the circle and then simply take the max of the entire array. It appears your radius is 41, centered at (50,50).
Then you could do
import numpy as np
xc, yc = 50, 50
length = 101
radius = 41
y_grid, x_grid = np.ogrid[-xc:length-xc, -yc:length-yc]
mask = x_grid ** 2 + y_grid ** 2 > radius ** 2
And now create your image. Then find the minimum value and set that to every value out side your boundary. If there is a pixel outside the circle that is bigger than the max inside the circle, it is now set to a much smaller value.
x_min = np.min(x)
x[mask] = x_min
So your image will look like
And now just take the max
print np.max(x)
6.4648628255130571
This solution is nice because it avoids loops, which pretty much defeats the purpose of using numpy in the first place.
EDIT:
Sorry you said you wanted the indices of the max. The above solution is the same just unravel the index.
>>> i, j = np.unravel_index(x.argmax(), x.shape)
>>> print "{} {}".format(i, j)
23 32
>>> np.max(x) == x[i,j]
True
circleList = []
indeces = []
for i in len(x[0]):
for j in len(x[1]):
if x[i,j] in circle: #However you check if pixel is inside circle
circleList.append(x[i,j])
indeces.append = ((i,j))
print np.max(circleList) #Here is your max
print indeces(np.argmax(circleList)) #Here are the indeces of the max
should do it.
I am trying to generate random x and y coordinates within a ring, which has an outer radius of 3.5 and an inner radius of 2. Therefor the following must be true for x and y:
x**2 + y**2 < 12.25 and x**2 + y**2 > 4
I wrote the following function:
def meteorites():
circle = False
while circle == False:
r = np.array([uniform(-6., 6.), uniform(-6., 6.)])
# we will regenerate random numbers untill the coordinates
# are within the ring x^2+y^2 < 3,5^2 and x^2+y^2 > 2^2
if (r[0]**2+r[1]**2 < 12.25) and (r[0]**2+r[1]**2 > 4.):
circle = True
else :
circle = False
return r[0], r[1]
x = np.zeros(1000)
y = np.zeros(1000)
for i in range(1000):
x[i] = meteorites()[0]
y[i] = meteorites()[1]
plt.scatter(x,y)
plt.show()
When I plot the resulting coordinates I get a square from -3.5 to 3.5. I can't seem to find the problem. I'm also not sure if it's a coding error, or some dum math problem. Since you guys are usually good at both, can you see what I'm doing wrong here?
To get uniform distribution of random point in the ring, one should take relative areas of thin circular regions into account. How it works for the circle
For your case generate uniform distribution of SquaredR in range of squared inner and outer radii. Pseudocode:
Fi = RandomUniform(0, 2 * Pi)
SquaredR = RandomUniform(inner*inner, outer*outer)
R = Sqrt(SquaredR)
x,y = R * Cos(Fi), R * Sin(Fi)
Take a random angle and a random distance between the two constraints; you'll need to produce a uniform distribution in a circle:
from math import sin, cos, radians, pi, sqrt
def meteorites():
angle = uniform(0, 2 * pi) # in radians
distance = sqrt(uniform(4, 12.25))
return distance * cos(angle), distance * sin(angle)
You're getting random points that don't fall on your ring because these two lines don't do what you want:
x[i] = meteorites()[0]
y[i] = meteorites()[1]
These assign an x value from one point on the ring to x[i], and the y value from a different point on the ring to y[i]. You get coordinates from different points because you're calling meteorites() twice.
Instead, you probably want to call the function once, and then assign to each coordinate, or do an assignment with iterable-unpacking where both targets are on the left side of the equals sign:
x[i], y[i] = meteorites()
Your implementation will also work if you correct one line: insstead of calling meteorites() twice, call just once.
x = np.zeros(1000)
y = np.zeros(1000)
for i in range(1000):
x[i], y[i] = meteorites()
plt.scatter(x,y)
plt.show()
I would also rather run through a loop that picks a random angle and a random distance within your ring range. Then calculate the coords from that.
But in your code the first problem is see is that should write:
x[i],y[i] = meteorites()
instead of
x[i] = meteorites()[0]
y[i] = meteorites()[1]
In your example, you're called meteorites() twice resulting in the x and y two different meteorites.
as #Martijn Pieters suggested, simply draw the polar coordinates uniformly in the range you require.
theta = uniform(0,2*np.pi)
r = uniform(2.,3.5)
x = r*np.cos(theta)
y = r*np.sin(theta)
EDIT: There will be equal probability for every point in the ring to occur.
But practically there will be less pixels for a given theta the closer r is to the lower limit. So "meteorites" with smaller r will occur with larger probability.
I believe this effect is negligeble.
EDIT 2: MBo's answer is better. Code:
theta = uniform(0, 2 * np.pi)
r = np.sqrt(uniform(2.0 ** 2, 3.5 ** 2)) # draw from sqrt distribution
x = r * np.cos(theta)
y = r * np.sin(theta)
You could try the following to generate 1000 samples using numpy:
import numpy
n = 1000
phi = numpy.random.uniform(0, 2*numpy.pi, n)
r = numpy.random.uniform(2, 3.5, n)
Then x, y coordinates can be constructed as follows using the transformation from radial to cartesian coordinates:
x = r * numpy.cos(phi)
y = r * numpy.sin(phi)
This shows the power of numpy, as x and y are now arrays without needing to iterate over n.
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')