How do I program a spatially dependend random number distribution? - python

I wrote a routine that distributes circles randomly (uniformly) with an arbitrary diameter in my study area.
def no_nearby_dots(new_dot, dots_sim, min_distance):
for dot in dots_sim:
if np.sqrt((dot[0] - new_dot[0]) ** 2 + (dot[1] - new_dot[1]) ** 2) <= min_distance:
return False
return True
while realizations < simulations:
dots_sim = []
new_dot = True
dots_sim.append((np.random.uniform(xmin, xmax), np.random.uniform(ymin, ymax)))
failed_attempts = 0
while new_dot:
xp = np.random.uniform(xmin, xmax)
yp = np.random.uniform(ymin, ymax)
if no_nearby_dots((xp, yp), dots_sim, diameter):
dots_sim.append((xp, yp))
failed_attempts = 0
else:
failed_attempts += 1
if len(dots_sim) == n_sim:
new_dot = False
if failed_attempts > 2000:
new_dot = False
print('ERROR...exit loop')
break
x_sim = [dot[0] for dot in dots_sim]
y_sim = [dot[1] for dot in dots_sim]
I want to introduce a second circle around the initial ones where the possibility of distributing points reduces exponentially towards the inner border -> I want to prevent a "hard" border, the points are allowed to occur anywhere on the plane but not closer than diameter, additionally they can only occur to a certain degree between diameter and diameter2.
Are there any ideas how to do that?

Here is an idea.
Choose a random radius between diameter/2 and diameter2/2, then generate a random point in the circle formed by that radius. There are many ways to choose a radius that meets your requirements. For example, the following chooses a radius such that radii very close to diameter2/2 are much more likely to be chosen:
radius = (diameter1/2) + ((diameter2/2) - (diameter1/2)) * random.random()**(1/20)
Note that 1/20 is the 20th root of a uniform (0, 1) random number. Try changing 1/20 to a different value and see what happens.
There are other ways to choose a radius this way, and they can all be described by a probability density function (for more information, see the following answer: Generate a random point within a circle (uniformly), which shows how a linear density function leads to a uniform distribution of points in a circle).

I solved it, and this is what I did:
while realizations < simulations:
dots_sim = []
new_dot = True
dots_sim.append((np.random.uniform(x_min, x_max), np.random.uniform(y_min, y_max)))
failed_attempts = 0
while new_dot:
x = np.random.uniform(x_min, x_max)
y = np.random.uniform(y_min, y_max)
diameter_simulation = np.random.uniform(min_diameter, max_diameter)
if no_nearby_dots((x, y), dots_sim, diameter_simulation):
dots_sim.append((x, y))
failed_attempts = 0
else:
failed_attempts += 1
if len(dots_sim) == len(x_coordinate):
new_dot = False
if failed_attempts > 1000:
new_dot = False
print('ERROR... -> no more space to place QDs! -> exit loop!')
break
What I did was creating diameters for my circles also using uniformly distributed numbers in an arbitrary interval, which smoothes my cumulative distribution function. This is the solution I needed, but it might not fit the initial question very well (or the question was formulated inaccurately in the first place :p)

Related

Find Pixels that Line Passes Over

I have the equation of a line in y=ax+b form, and a starting point, and I want to find all the pixels this line crosses over/into.
At the moment, I was just stepping the x value a bit, calculating y, truncating to find pixel index and adding it to a list if not already in the list, and continuing until reaching a destination point. Kind of as follows (python/pseudocode):
temp_x = start_x
prev_tested = None
pixel_list = []
while(not at destination):
temp_y = ... find y from x and line equation
pixel = (int(temp_y), int(temp_x))
if pixel is not the same as the prev_pixel:
pixel_list.append(pixel)
temp_x += some_step_value
But this just seems wildly inaccurate and inefficient (No need to tell me that in the answers, I understand this is a bad algo). The step value affects a lot. Too large and I will miss pixels (especially if there is a large slope). Too small and I am just wasting iterations. I figured that I can make my step value proportional to my slope, so that I try to minimize the number of iterations I have to do, while also not skipping over too much. But it is not perfect, still skipping over pixels that the line only barely enters the corner.
I feel like there has to be some kind of way to just absolutely determine which pixels a line is touching, but I have had no look finding anything myself. Is there some resource anyone could point me towards that could help with this?
Dx= X1 - X0
Dy= Y1 - Y0
D= Max(Abs(Dx), Abs(Dy))
for I= 0 to D
X= X0 + I * Dx / D
Y= Y0 + I * Dy / D
works in all cases (except the degenerate D=0) to join (X0, Y0) to (X1, Y1) using integer coordinates.
Technical note:
You can avoid the two divisions. One by the fact that the fraction simplifies to ±1, and the other by computing the quotient and remainder incrementally.
If you believe that this is not accurate enough, you can scale all coordinates by an arbitrary integer M, compute the points with step M and divide the coordinates by M.
Your step value should be always 1 . What to step over, however depends on your line being more on the horizontal or more on the vertical (that is "a < 1" or "a > 1". For one, step x on 1, and y will be a fraction of that, and for lines more on vertical, y will step with 1 and x will be a fraction of that.
def draw_line(a, b, start_x, destination):
result = []
x = start_x
y = a * x + b
result.append((int(x),int(y)))
while int(x) != destination[0] and int(y) != destination[1]:
if abs(a) < 1:
x += 1 if destination[0] > start_x else -1
y += (1 / a) if a!= 0 else 0
else:
y += 1 if destination[1] > y else -1
x += 1 / a
result.append((int(x), int(y)))
return result
# maybe there is some missing corner case for vertical lines.

My implementation of Bridson's algorithm Poisson-Disk Sampling seems to be stuck in an infinite loop

A video by Sebastion Lague explained the Bridson's algorithm really well.
To oversimplify,
Create cell grid that has sides of radius/sqrt(2).
Place initial point and list as spawnpoint.
Place point into cell in grid.
For any spawnpoint, spawn a point between radius and 2*radius.
Look at the cells 2 units away from cell of new point.
If contains other points, compare distance.
If any point is closer to new point than the radius, new point is invalid.
If new point is valid, new point is listed as spawnpoint and placed into cell in grid.
If spawnpoint spawns too many invalid points, spawnpoint is removed and turns into point.
Repeat until no more spawnpoints exists.
Return points.
I basically written the same thing down in Python 3.7.2 and pygame 1.7~, but as said in the title, I'm stuck in recursive purgatory.
I used one Point() class for this algorithm, which might seem redundant given that pygame.Vector2() exists, but I needed some elements for a separate algorithm (Delaunay's with infinite vertices) that required this class to work.
For the sake of simplicity I'm going to cut away all the Delaunay-specific elements and show the bare-bones of this class that is needed for this algorithm:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def DistanceToSquared(self,other):
return (self.x-other.x)**2 + (self.y-other.y)**2
The code that is related to the Bridson's algorithm is:
def PoissonDiskSampling(width, height, radius, startPos = None, spawnAttempts = 10):
if startPos == None:
startPos = [width//2,height//2]
cellSize = radius / math.sqrt(2)
cellNumberX = int(width // cellSize + 1) # Initialise a cells grid for optimisation
cellNumberY = int(height // cellSize + 1)
cellGrid = [[None for x in range(cellNumberX)] for y in range(cellNumberY)]
startingPoint = Point(startPos[0],startPos[1]) # Add an iniial point for spawning purposes
cellGrid[startingPoint.x//radius][startingPoint.y//radius] = startingPoint
points = [startingPoint] # Initialise 2 lists tracking all points and active points
spawnpoints = [startingPoint]
while len(spawnpoints) > 0:
spawnIndex = random.randint(0,len(spawnpoints)-1)
spawnpoint = spawnpoints[spawnIndex]
spawned = False
for i in range(spawnAttempts):
r = random.uniform(radius,2*radius)
radian = random.uniform(0,2*math.pi)
newPoint = Point(spawnpoint.x + r*math.cos(radian),
spawnpoint.y + r*math.sin(radian))
if 0 <= newPoint.x <= width and 0 <= newPoint.y <= height:
isValid = True
else:
continue
newPointIndex = [int(newPoint.x//cellSize), int(newPoint.y//cellSize)]
neighbours = FindNeighbours(cellNumberX,cellNumberY,newPointIndex,cellGrid)
for neighbour in neighbours:
if newPoint.DistanceToSquared(neighbour) < radius**2:
isValid = False
break
if isValid:
points.append(newPoint)
spawnpoints.append(newPoint)
spawned = True
break
else:
continue
if spawned == False:
spawnpoints.remove(spawnpoint)
return points
def FindNeighbours(cellNumberX, cellNumberY, index, cellGrid):
neighbours = []
for cellX in range(max(0,(index[0]-2)), min(cellNumberX,(index[1]+2))):
for cellY in range(max(0,(index[0]-2)), min(cellNumberY,(index[1]+2))):
if cellGrid[cellX][cellY] != None:
neighbours.append(cellGrid[cellX][cellY])
return neighbours
Please help.
The probably most important step is missing in your code:
If new point is valid, new point is listed as spawnpoint and placed into cell in grid.
I suggest to add the point to the cellGrid if it is valid:
if isValid:
cellGrid[newPointIndex[0]][newPointIndex[1]] = newPoint
points.append(newPoint)
spawnpoints.append(newPoint)
spawned = True
break
Further, you have to verify if the cell with the index newPointIndex is not already occupied before a point can be add:
newPointIndex = [int(newPoint.x/cellSize), int(newPoint.y/cellSize)]
if cellGrid[newPointIndex[0]][newPointIndex[1]] != None:
continue
neighbours = FindNeighbours(cellNumberX,cellNumberY,newPointIndex,cellGrid)
Finally there is an issue in the function FindNeighbours. range(start, stop) creates a range for x in start <= x < stop.
So the stop has to be index[0]+3 rather than index[0]+2.
Further the ranges which control the 2 nested for loops, run both from x-2 to y+2 rather than from x-2 to x+2 respectively from y-2 to y+2:
for cellX in range(max(0,(index[0]-2)), min(cellNumberX,(index[1]+2))):
for cellY in range(max(0,(index[0]-2)), min(cellNumberY,(index[1]+2)))
The fixed function has to be:
def FindNeighbours(cellNumberX, cellNumberY, index, cellGrid):
neighbours = []
for cellX in range(max(0, index[0]-2), min(cellNumberX, index[0]+3)):
for cellY in range(max(0, index[1]-2), min(cellNumberY, index[1]+3)):
if cellGrid[cellX][cellY] != None:
neighbours.append(cellGrid[cellX][cellY])
return neighbours
See the result, for a size of 300 x 300 and a radius of 15:
An even better result can be achieve, if always the 1st point of spawnpoints is used to rather than a random point:
# spawnIndex = random.randint(0,len(spawnpoints)-1)
spawnIndex = 0 # 0 rather than random
spawnpoint = spawnpoints[spawnIndex]

Calculate PI using Random Numbers

Having trouble with the following question:
In geometry the ratio of the circumference of a circle to its diameter is known as π. The value of π can be estimated from an infinite series of the form:
π / 4 = 1 - (1/3) + (1/5) - (1/7) + (1/9) - (1/11) + ...
There is another novel approach to calculate π. Imagine that you have a dart board that is 2 units square. It inscribes a circle of unit radius. The center of the circle coincides with the center of the square. Now imagine that you throw darts at that dart board randomly. Then the ratio of the number of darts that fall within the circle to the total number of darts thrown is the same as the ratio of the area of the circle to the area of the square dart board. The area of a circle with unit radius is just π square unit. The area of the dart board is 4 square units. The ratio of the area of the circle to the area of the square is π / 4.
To simuluate the throwing of darts we will use a random number generator. The Random module has several random number generating functions that can be used. For example, the function uniform(a, b) returns a floating point random number in the range a (inclusive) and b (exclusive).
Imagine that the square dart board has a coordinate system attached to it. The upper right corner has coordinates ( 1.0, 1.0) and the lower left corner has coordinates ( -1.0, -1.0 ). It has sides that are 2 units long and its center (as well as the center of the inscribed circle) is at the origin.
A random point inside the dart board can be specified by its x and y coordinates. These values are generated using the random number generator. The way we achieve that is:
xPos = random.uniform (-1.0, 1.0)
yPos = random.uniform (-1.0, 1.0)
To determine if a point is inside the circle its distance from the center of the circle must be strictly less than the radius of the circle. The distance of a point with coordinates ( xPos, yPos ) from the center is math.hypot (xPos, yPos). The radius of the circle is 1 unit.
The program that you will be writing will be called CalculatePI. It will have the following structure:
import math
import random
def computePI ( numThrows ):
...
def main ():
...
main()
Your function main() will call the function computePI() for a given number of throws. The function computePI() will simulate the throw of a dart by generating random numbers for the x and y coordinates. You will determine if that randomly generated point is inside the circle or not. You will do this as many times as specified by the number of throws. You will keep a count of the number of times a dart lands within the circle. That count divided by the total number of throws is the ratio π/4. The function computePI() will then return the computed value of PI.
In your function main() you want to experiment and see if the accuracy of PI increases with the number of throws on the dartboard. You will compare your result with the value given by math.pi. The quantity Difference in the output is your calculated value of PI minus math.pi. Use the following number of throws to run your experiment - 100, 1000, 10,000, 100,000, 1,000,000, and 10,000,000. You will call the function computePI() with these numbers as input parameters. Your output will be similar to the following, i.e. the actual values of your Calculated PI and Difference will be different but close to the ones shown:
Computation of PI using Random Numbers
num = 100 Calculated PI = 3.320000 Difference = +0.178407
num = 1000 Calculated PI = 3.080000 Difference = -0.061593
num = 10000 Calculated PI = 3.120400 Difference = -0.021193
num = 100000 Calculated PI = 3.144720 Difference = +0.003127
num = 1000000 Calculated PI = 3.142588 Difference = +0.000995
num = 10000000 Calculated PI = 3.141796 Difference = +0.000204
Difference = Calculated PI - math.pi
Your output must be in the above format. The number of throws must be left justified. The calculated value of π and the difference must be expressed correct to six places of decimal. There should be plus or minus sign on the difference. Read the relevant sections in the book on formatting.
Till now I have done:
import math
import random
def computePI (numThrows):
xPos = random.uniform (-1.0, 1.0)
yPos = random.uniform (-1.0, 1.0)
in_circle = 0
throws = 0
while (throws < numThrows):
if math.hypot (xPos, yPos) <= 1:
in_circle += 1
throws += 1
pi = (4 * in_circle) / numThrows
return pi
def main ():
throws = (100, 1000, 10000, 100000, 1000000, 10000000)
for numThrows in throws[0:7]:
main ()
I am having trouble calling the ComputePI function in the Main function. Also how do I print num with left indentation and ensure that all numbers have the required decimal space? Thank you!
Your program has three main issues:
Generating random numbers in the wrong place
xPos = random.uniform (-1.0, 1.0)
yPos = random.uniform (-1.0, 1.0)
These lines are executed only once when you enter the computePI() function. You then proceed to calculate the exact same value of hypot for hundreds or even thousands of iterations. Put these lines inside the while loop.
Integer arithmetic
pi = (4 * in_circle) / numThrows
Since in_circle and numThrows are both integers, this calculation will be performed using integer arithmetic (in Python 2, at least). Changing the constant from 4 to 4.0 will change this to a floating point calculation:
pi = (4.0 * in_circle) / numThrows
Incomplete main() function:
There's no need to use a subset of your throws tuple, and you haven't added a body to your for loop. Try this:
for numThrows in (100, 1000, 10000, 100000, 1000000, 10000000):
randpi = computePI(numThrows)
diff = randpi - math.pi
print "num = %-8d Calculated PI = %8.6f Difference = %+9.6f" % \
(numThrows, randpi, diff)
This is how I find it easy.
import random
import math
def approximate_pi():
total_points = 0
within_circle = 0
for i in range (10000):
x = random.random()
y = random.random()
total_points += 1
distance = math.sqrt(x**2+y**2)
if distance < 1:
within_circle += 1
if total_points % 1000 == 0:
pi_estimate = 4 * within_circle / total_points
yield pi_estimate
set total point generated and points withing the circle to zero
total_points = 0
within_circle = 0
generate the random values of x and y for multiple times. Calculate the distance of the point from the center of the circle or (0,0). Then if the distance is less than one it means that it's within the circle so it is incremented.
distance = math.sqrt(x**2+y**2)
if distance < 1:
within_circle += 1
Now if you have generated let's say multiple of 1000(1000 because we have taken the range for 10,000 so 1000 to get 10 values of pi), calculate the estimated value of pi using this formula which you know already.and the tied the estimate value(pi_estmate)
if total_points % 1000 == 0:
pi_estimate = 4 * within_circle / total_points
yield pi_estimate
pi_estimates = list(es for es in approximate_pi())
errors = list(estimate-math.pi for estimate in approximate_pi())
print(pi_estimates)
print(errors)
OUTPUT:
Estimates
[3.096, 3.142, 3.1253333333333333, 3.121, 3.1384, 3.136, 3.1314285714285712, 3.133, 3.1342222222222222]
Errors
[0.04240734641020705, 0.02240734641020703, 0.03307401307687341, 0.020407346410206806, 0.02320734641020694, 0.0017406797435404187, -0.009021225018364554, -0.011592653589793223, -0.016703764700904067]
Hope you understood, I hope my explanation was easy to understand, I am a beginner and learning stuff if there is anything wrong please feel free to notify.
Thank you
Essentially what the statement you've written above says:
import math
def find_pi(iterations):
return sum(
1 for _ in range(iterations) if math.hypot(
random.random(), random.random()) <= 1) * 4.0/iterations

White non-gird lines on matplotlib plot

I have been working on a project for my math class in which I am creating a Julia set generator. I had finally succeeded in generating it, but when I show the plot and save the image generated there are white lines all over it. They do not line up with x ticks or y ticks. When I view the saved image it had even more white lines. I have been searching to find what these might be, but I have found nothing.
import matplotlib.pyplot as plt
#Set up window with a name and size.
plt.figure("Julia Set Generator by Eric Kapilik", figsize=(7.0,7.0))
#Set Range of axes
plt.xlim([-2,2])
plt.ylim([-2,2])
#Add labels to axes
plt.xlabel("Real Numbers")
plt.ylabel("Imaginary Numbers")
plt.grid(b=False, which = "major", axis = "both")
plt.grid(b=False, which = "minor", axis = "both")
name = input("Save file as... \n")
#Ask for maximum amount of iterations to base colour
#selection off of with fractions.
max = int(input("What is the maximum amount of iterations you wish to run? "))
#Generate an array of colour names to be used to plot points.
#Set seed of array.
colourArray = ["r"]
for colourNum in range (1, max):
#if the place in array is between 0% and 25% then set the colour to red.
#Same method used for other three colours.
if colourNum >= 0 and colourNum <= (max/4):
colourArray.append("r") #red
elif colourNum > (max/4) and colourNum <= (max/2):
colourArray.append("y") #yellow
elif colourNum > (max/2) and colourNum <= ((3*max)/4):
colourArray.append("g") #green
elif colourNum > ((3*max)/4) and colourNum <= max:
colourArray.append("c") #cyan
#Get constant value of which the julia set is based off of.
#The real number component is plotted on the horizontal axis
#of a complex number grid so we will use x.
xConstant = float(input("Enter real number constant component: "))
#The imaginary nuber compenent of a complex number is plotted on the vertical axis,
#so we will use y in our real number grid (for simplicity's sake).
yConstant = float(input("Enter imaginary number constant component: "))
#Title the graph based on the constatn complex number entered.
plt.title(str(xConstant) + " + " + str(yConstant) + "i")
#See the starting coordinates to be tested and conditions
xTest = float(-2)
yTest = float(2)
stop = False
i = 0
xPrevious = xTest
yPrevious = yTest
#Using an escape time algorith, determine the amout of iterations of the recursion
#are needed for the coordinate to be attarcted to infinity.
#Continue doing this while the y value of the coordinate being tested is less
#than or equal to -2.
while yTest >= -2:
#We are following the recursive function of
#f(Z-1) = Z^2 + C
#Where Z is current coordinate, and C is the constant value.
#Reminder: Both Z and C are actually complex numbers but in our case we
#are using them both as real number coordinates on a real number grid.
xCurrent = ((xPrevious**2) - (yPrevious**2)) + xConstant
yCurrent = (2 * xPrevious * yPrevious) + yConstant
#Points that surpass a circle of radius 2 with a centre point at the origin
#are considered to indefinitely escape to infinity.
#So when the radius of the recursive coordinate based off of the tested coordinate
#becomes greater or equal to two we know it will be attaracted to infinity.
radius = xCurrent**2 + yCurrent**2
#"Is the point an escapee?"
if radius >= 2:
#Since the point has been defined as one that esacpes to infintity
#it is considered an escapee, so set that to true.
escapee = True
#"Is the point a prisoner?"
if i == max:
#The point is considered a prisoner if max iterations is reached and
#the point is still within the circle of radius 2.
#The testeed point will be considered a prisoner based off of the amount
#of iterations we selected, it is possible that with more iterations
#that we would find this to be an escapee.
prisoner = True
#If we have not defined what kind of point this is yet, then go to the next
#iteration of the recursion. i is the number of iterations completed for
#the test point.
if escapee == False and prisoner == False:
i = i + 1
#Out with the old, in with the new. Set the current points to the previous
#points for the next iteration.
xPrevious = xCurrent
yPrevious= yCurrent
#If, however, we have defined the point, then colour it based off of
#the amount of iterations using the array of colours generated at the
#beginning to select the colour.
if escapee == True or prisoner == True:
#This sets the black points that are prisoners, this is the body
#of the julia set.
if i == max:
colourPoint = "k,"
else:
#Colour the point and concatenate a ",", which means to plot this point
#as a pixel.
colourPoint = colourArray[i] + ","
#Plot the point! (Most satisfying part)
plt.plot(xTest, yTest, colourPoint)
#Determine the percentage finished, to give user an idea of how the
#renderig is going. (Not nessecary, but appreciable)
percent = int(((yTest-2)/4) * (-100))
print(str(percent) + "%")
#After setting a colour and plotting the point, jump to the next test coordinate.
#Once the end of the line is reached, jump down one.
if xTest >= 2:
xTest = -2
yTest = yTest - 0.01
else:
xTest= xTest + 0.01
#Reset the starting conditions.
i = 0
escapee = False
prisoner = False
xPrevious = xTest
yPrevious = yTest
#Show the beauty.
print("100%")
print("Wait for matplotlib to finish things up...\nWill take a minute...")
plt.show()
plt.savefig(name)
Plot that was generated:
Saved image, with even more white lines:

Generating multiple random (x, y) coordinates, excluding duplicates?

I want to generate a bunch (x, y) coordinates from 0 to 2500 that excludes points that are within 200 of each other without recursion.
Right now I have it check through a list of all previous values to see if any are far enough from all the others. This is really inefficient and if I need to generate a large number of points it takes forever.
So how would I go about doing this?
This is a variant on Hank Ditton's suggestion that should be more efficient time- and memory-wise, especially if you're selecting relatively few points out of all possible points. The idea is that, whenever a new point is generated, everything within 200 units of it is added to a set of points to exclude, against which all freshly-generated points are checked.
import random
radius = 200
rangeX = (0, 2500)
rangeY = (0, 2500)
qty = 100 # or however many points you want
# Generate a set of all points within 200 of the origin, to be used as offsets later
# There's probably a more efficient way to do this.
deltas = set()
for x in range(-radius, radius+1):
for y in range(-radius, radius+1):
if x*x + y*y <= radius*radius:
deltas.add((x,y))
randPoints = []
excluded = set()
i = 0
while i<qty:
x = random.randrange(*rangeX)
y = random.randrange(*rangeY)
if (x,y) in excluded: continue
randPoints.append((x,y))
i += 1
excluded.update((x+dx, y+dy) for (dx,dy) in deltas)
print randPoints
I would overgenerate the points, target_N < input_N, and filter them using a KDTree. For example:
import numpy as np
from scipy.spatial import KDTree
N = 20
pts = 2500*np.random.random((N,2))
tree = KDTree(pts)
print tree.sparse_distance_matrix(tree, 200)
Would give me points that are "close" to each other. From here it should be simple to apply any filter:
(11, 0) 60.843426339
(0, 11) 60.843426339
(1, 3) 177.853472309
(3, 1) 177.853472309
Some options:
Use your algorithm but implement it with a kd-tree that would speed up nearest neighbours look-up
Build a regular grid over the [0, 2500]^2 square and 'shake' all points randomly with a bi-dimensional normal distribution centered on each intersection in the grid
Draw a larger number of random points then apply a k-means algorithm and only keep the centroids. They will be far away from one another and the algorithm, though iterative, could converge more quickly than your algorithm.
This has been answered, but it's very tangentially related to my work so I took a stab at it. I implemented the algorithm described in this note which I found linked from this blog post. Unfortunately it's not faster than the other proposed methods, but I'm sure there are optimizations to be made.
import numpy as np
import matplotlib.pyplot as plt
def lonely(p,X,r):
m = X.shape[1]
x0,y0 = p
x = y = np.arange(-r,r)
x = x + x0
y = y + y0
u,v = np.meshgrid(x,y)
u[u < 0] = 0
u[u >= m] = m-1
v[v < 0] = 0
v[v >= m] = m-1
return not np.any(X[u[:],v[:]] > 0)
def generate_samples(m=2500,r=200,k=30):
# m = extent of sample domain
# r = minimum distance between points
# k = samples before rejection
active_list = []
# step 0 - initialize n-d background grid
X = np.ones((m,m))*-1
# step 1 - select initial sample
x0,y0 = np.random.randint(0,m), np.random.randint(0,m)
active_list.append((x0,y0))
X[active_list[0]] = 1
# step 2 - iterate over active list
while active_list:
i = np.random.randint(0,len(active_list))
rad = np.random.rand(k)*r+r
theta = np.random.rand(k)*2*np.pi
# get a list of random candidates within [r,2r] from the active point
candidates = np.round((rad*np.cos(theta)+active_list[i][0], rad*np.sin(theta)+active_list[i][1])).astype(np.int32).T
# trim the list based on boundaries of the array
candidates = [(x,y) for x,y in candidates if x >= 0 and y >= 0 and x < m and y < m]
for p in candidates:
if X[p] < 0 and lonely(p,X,r):
X[p] = 1
active_list.append(p)
break
else:
del active_list[i]
return X
X = generate_samples(2500, 200, 10)
s = np.where(X>0)
plt.plot(s[0],s[1],'.')
And the results:
Per the link, the method from aganders3 is known as Poisson Disc Sampling. You might be able to find more efficient implementations that use a local grid search to find 'overlaps.' For example Poisson Disc Sampling. Because you are constraining the system, it cannot be completely random. The maximum packing for circles with uniform radii in a plane is ~90% and is achieved when the circles are arranged in a perfect hexagonal array. As the number of points you request approaches the theoretical limit, the generated arrangement will become more hexagonal. In my experience, it is difficult to get above ~60% packing with uniform circles using this approach.
the following method uses list comprehension, but I am generating integers you can use different random generators for different datatypes
arr = [[random.randint(-4, 4), random.randint(-4, 4)] for i in range(40)]

Categories