91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
'''
|
|
Poisson Distribution
|
|
'''
|
|
from math import pi, sqrt, sin, cos, hypot
|
|
from random import random, randint
|
|
|
|
|
|
tau = 2 * pi
|
|
|
|
|
|
def poisson(w, h, d, test_points=30):
|
|
'''
|
|
Return a list of coordinates generated by a Poisson point process.
|
|
|
|
Return a list of two-tuples, pairs of ints, representing points in a 2D
|
|
plane. The points are bounded by the width and height (w and h) and
|
|
separated by a minimum distance of d.
|
|
'''
|
|
grid, x, y = Grid(d), randint(0, w), randint(0, h)
|
|
grid.add(x, y)
|
|
todo = [(x, y)]
|
|
while todo:
|
|
x, y = todo.pop(0)
|
|
todo.extend(
|
|
(nx, ny)
|
|
for nx, ny in candidates(x, y, d, test_points)
|
|
if 0 <= nx <= w and 0 <= ny <= h and grid.add(nx, ny)
|
|
)
|
|
return list(grid.grid.values())
|
|
|
|
|
|
class Grid(object):
|
|
'''
|
|
Keep track of points in a plane and efficiently check that added points
|
|
are not nearer than a certain distance d to any others.
|
|
'''
|
|
|
|
def __init__(self, d):
|
|
self.d = d
|
|
self.r = d / sqrt(2) # A square of side r and diagonal d.
|
|
self.grid = {} # Map from grid "bins" to points, 1-to-1.
|
|
|
|
def add(self, x, y):
|
|
'''
|
|
Add a point pair if it's not too close to any others and return a
|
|
bool: True if the point was added, False if it was rejected.
|
|
'''
|
|
grid_coords = round(x / self.r), round(y / self.r)
|
|
if any(hypot(x - nx, y - ny) < self.d
|
|
for nx, ny in self._neighborhood(grid_coords)):
|
|
return False
|
|
self.grid[grid_coords] = x, y
|
|
return True
|
|
|
|
def _neighborhood(self, coordinates):
|
|
'''Return a list of the points in the neighborhood of (x, y).'''
|
|
# Note, x and y here are grid coordinates, not plane coordinates.
|
|
# This method returns plane coordinates.
|
|
x, y = coordinates
|
|
return filter(None, map(self.grid.get, [
|
|
(x-1, y-2), (x, y-2), (x+1, y-2),
|
|
(x-2, y-1), (x-1, y-1), (x, y-1), (x+1, y-1), (x+2, y-1),
|
|
(x-2, y), (x-1, y), (x, y), (x+1, y), (x+2, y),
|
|
(x-2, y+1), (x-1, y+1), (x, y+1), (x+1, y+1), (x+2, y+1),
|
|
(x-1, y+2), (x, y+2), (x+1, y+2),
|
|
]))
|
|
|
|
|
|
def candidates(x, y, d, test_points):
|
|
'''Generate points at a distance from (x, y) from d to 2*d.'''
|
|
for _ in range(test_points):
|
|
radius = d + random() * d
|
|
angle = random() * tau
|
|
yield (
|
|
int(round(x + radius * cos(angle))),
|
|
int(round(y + radius * sin(angle))),
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
from random import seed
|
|
|
|
seed(23)
|
|
|
|
WIDTH, HEIGHT, DISTANCE = 80, 24, 4
|
|
|
|
P = set(poisson(WIDTH, HEIGHT, DISTANCE))
|
|
|
|
for y in range(HEIGHT + 1):
|
|
print(''.join(' *'[(x, y) in P] for x in range(WIDTH + 1)))
|