-
Notifications
You must be signed in to change notification settings - Fork 5
/
distributions.py
181 lines (140 loc) · 4.8 KB
/
distributions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""
Functions for generating points.
All functions return a list of Point objects.
"""
from random import randrange
from math import ceil, sqrt, gcd
from geometry import Point
def generate_random_points(count, area, scale=1, decluster=True):
"""
Generate points at random locations using python's random module.
Arguments:
count is the number of points to generate (int)
area is a 2-tuple of the maximum x and y values of the field
scale is a value that describes how much of the area to fill with points
-- values greater than 1 will result in points outside the area
-- values less than 1 result in points bounded by scale*area
-- default is 1
decluster is a boolean flag; points will be de-clustered if True (default)
Returns:
A list of Point objects.
"""
cluster_fraction = 1
if decluster:
# Generate extra points for declustering later
cluster_fraction = 2
# Scale the area
bound_x, bound_y = int(area[0]*scale), int(area[1]*scale)
n_extra_points = int(cluster_fraction*count)
points = [
Point(int(randrange(bound_x)), int(randrange(bound_y)))
for __ in range(0, n_extra_points)
]
# Translate the points so that some are in negative x and y for nicer edges
dx = (bound_x - area[0]) / 2
dy = (bound_y - area[1]) / 2
points = [Point(p.x - dx, p.y - dy) for p in points]
return points
def generate_rectangular_points(count, area):
"""
Generate a rectangular grid of points.
Arguments:
count is the approximate number of points to generate (int)
area is a 2-tuple of the maximum x and y values of the field
Returns:
A list of Point objects
"""
# Reduce the area to lowest terms and calculate grid spacing
k = gcd(area[0], area[1])
reduced_x, reduced_y = area[0]/k, area[1]/k
aspect = reduced_x / reduced_y
count_x = ceil(sqrt(count)*aspect)
count_y = ceil(sqrt(count)/aspect)
x_spacing = int(max(ceil(area[0]/count_x), 1))
y_spacing = int(max(ceil(area[1]/count_y), 1))
return [
Point(x, y)
for y in range(0, area[1], y_spacing)
for x in range(0, area[0], x_spacing)
]
def generate_equilateral_points(count, area):
"""
Generate a set of points that will triangulate to equilateral triangles.
To get equilateral triangles, the grid has to be offset so that every
other line is advanced by half the x-spacing. For example, a grid like
* * * *
* * *
* * * *
Will result in equilateral triangles.
Arguments:
n_points is the number of points to generate
area is a 2-tuple describing the maximum x and y values of the field
Returns:
A list of Point objects
"""
points = []
# Figure out roughly how many points we need in x
count_x = ceil(sqrt(count))
# Calculate the spacing
x_spacing = max(ceil(area[0]/count_x), 1)
x_offset = x_spacing/2
y_spacing = sqrt(x_spacing**2 - (x_spacing/2)**2)
# Generate the points
xmax = area[0]+x_spacing+x_offset
ymax = area[1]+y_spacing
y = 0
odd_row = False
while y <= ymax:
# Offset every other row for equilateral triangles
if odd_row:
x = -x_offset
else:
x = 0
while x <= xmax:
points.append(Point(int(x), int(y)))
x += x_spacing
odd_row = not odd_row
y += y_spacing
return points
def generate_halton_points(count, area, p=2, q=3):
"""
Generate points using the p,q Halton sequence.
https://en.wikipedia.org/wiki/Halton_sequence
A Halton sequence is a quasi-random sequence that uses one prime number for
each dimension as a base. For example, the 2,3 Halton sequence
(the default) looks like this:
(1/2, 1/3), (1/4, 2/3), (3/4, 1/9), (1/8, 4/9), ...
These values are then scaled to fill the given area.
Arguments:
count is the number of points to generate
area is a 2-tuple of the maximum x and y values of the field
p is the first prime (default 2)
q is the second prime (default 3)
Returns:
A list of Point objects
"""
points = []
for i in range(1, count+1):
fx = 1
fy = 1
ix = i
iy = i
rx = 0
ry = 0
# Calculate the ith p- and q-values
while ix > 0:
fx /= p
rx += fx * (ix % p)
ix = int(ix/p)
while iy > 0:
fy /= q
ry += fy * (iy % q)
iy = int(iy/q)
# Scale the point and add to the list
points.append(Point(rx*area[0], ry*area[1]))
# Add four anchor points at the corners
points.append(Point(0, 0))
points.append(Point(0, area[1]))
points.append(Point(area[0], area[1]))
points.append(Point(area[0], 0))
return points