Метод равномерного случайного заполнения диска точками в python

У меня есть приложение, для которого требуется диск, заполненный "n" точками в квази-случайном порядке. Я хочу, чтобы точки были несколько случайными, но все еще имеют более или менее регулярную плотность по диску.

Мой текущий метод заключается в том, чтобы поместить точку, проверить, находится ли она внутри диска, а затем проверить, достаточно ли она далеко от всех остальных сохраненных пунктов. Мой код ниже:

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()

Чтобы получить самую ровную плотность точек, я фактически итеративно запускаю этот script с помощью другого script, при этом критерии "близости" уменьшаются для каждой последующей итерации. В какой-то момент script не может закончиться, и я просто использую точки последней успешной итерации.

Итак, мой вопрос довольно широк: есть ли лучший способ сделать это? Мой метод сейчас в порядке, но моя кишка говорит, что есть лучший способ создать такое поле точек.

Ниже приведена иллюстрация вывода: один с критериями высокой близости, а другой с критериями близости "самый низкий найденный" (что я хочу).

enter image description here

enter image description here

Ответ 1

Если у вас есть определенная область, такая как диск (круг), в которой вы хотите генерировать случайные точки, вам лучше использовать уравнение для круга и ограничить радиус:

x^2 + y^2 = r^2  (0 < r < R)

или параметризован для двух переменных

cos(a) = x/r
sin(a) = y/r
sin^2(a) + cos^2(a) = 1

Чтобы создать нечто вроде псевдослучайного распределения с низкой плотностью, вы должны принять следующий подход:

Для случайно распределенных диапазонов r и a выберите n точек.

Это позволяет вам генерировать распределение, чтобы оно соответствовало вашим критериям плотности.

Чтобы понять, почему это работает, представьте, что ваш круг сначала разделен на маленькие кольца длиной dr, теперь представьте, что ваш круг разделен на кусочки пирога с углом da. Ваша случайность теперь имеет равную вероятность по всей площади в коробке вокруг круга. Если вы разделите области разрешенной случайности во всем круге, вы получите более равномерное распределение по всему кругу и небольшую случайную вариацию для отдельных областей, дающую вам псо-случайный внешний вид и чувство, которым вы после.

enter image description here

Теперь ваша задача - генерировать n точки для каждой данной области. Вы хотите, чтобы n зависела от r, поскольку площадь каждого подразделения изменяется по мере того, как вы выходите из круга. Вы можете соотносить это с точным изменением области, в которую приносит каждое пространство:

для n -th to n + 1 -ного кольца:

d(Area,n,n-1) = Area(n) - Area(n-1)

Площадь любого заданного кольца:

Area = pi*(dr*n)^2 - pi*(dr*(n-1))

Таким образом, разница становится:

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]

Вы можете изложить это, чтобы получить представление о том, сколько n должно увеличиться, но может быть скорее просто угадать процентное увеличение (30%) или что-то в этом роде.

Примером, который я предоставил, является небольшое подмножество, и уменьшение da и dr значительно улучшит ваши результаты.

Вот пример грубого кода для создания таких точек:

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

Я тестировал его с параметрами:

n_rings = 20
n_angles = 20
base_points = .9
increase_per_level = 1.1

И получил следующие результаты:

enter image description here

Он выглядит более плотным, чем ваш предоставленный образ, но я думаю, что дальнейшая настройка этих переменных может быть полезной.

Вы можете добавить дополнительную часть, чтобы правильно масштабировать плотность, вычислив количество точек на кольцо.

points_per_ring = densitymath.pi(dr ** 2) * (2 * n + 1)  points_per_division = points_per_ring/n_angles

Это обеспечит еще лучшее масштабированное распределение.

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

Получение лучших результатов с использованием следующих параметров

R = 1.
n_rings = 10.
n_angles = 10.
density = 10/(dr*da)   # ~ ten points per unit area

С графиком...

enter image description here

и для удовольствия вы можете графически отображать дивизии, чтобы увидеть, насколько они соответствуют вашему распределению и настраиваются.

enter image description here

Ответ 2

Простое решение, основанное на Выбор точки диска из 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()

Он дает random points plot.

В качестве альтернативы вы также можете создать обычную сетку и исказить ее случайным образом:

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()

Он дает distorted triangles

Треугольники просто для визуализации. Очевидным недостатком является то, что в зависимости от вашего выбора сетки, как в середине, так и на границах (как показано здесь), из-за дискретизации сетки будут более или менее крупные "дыры".

Ответ 3

В зависимости от того, насколько случайны точки, они могут быть достаточно простыми, чтобы просто создать сетку точек на диске, а затем вытеснить каждую точку с помощью небольшого, но случайного количества.

Ответ 4

Возможно, вам нужно больше случайности, но если вы просто хотите заполнить свой диск четким распределением точек, которые не находятся на очевидной сетке, вы можете попробовать спираль со случайной фазой.

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()

enter image description here

Ответ 5

Теория вероятности гарантирует, что метод отклонения является подходящим методом для генерации равномерно распределенных точек внутри диска D (0, r) с центром в начале координат и радиусом r. А именно, вы создаете точки внутри квадрата [-r, r] x [-r, r], пока точка не окажется внутри диска:

do{
  generate P in [-r,r]x[-r,r];
  }while(P[0]**2+P[1]**2>r);
  return P;

unif_rnd_disk - это функция-генератор, реализующая этот метод отклонения:

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')

Если мы хотим создать точки в диске, центрированном на C (a, b), мы должны применить перевод к точкам в круге 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')