The pythonic way of generating random, uniformly distributed points in a hollow square plate

Suppose we have a hollow square plate of size n. That is, we have a square nxn from which the rectangle k * l (1 <= k, l <= n-2) was removed. I want to calculate the average distance between 2 random, evenly distributed points inside such a hollow square plate. For simplicity, consider n = 3, k = l = 1, or a 3x3 square from the center of which a unit square is removed

I wrote this code for numpy, but it has at least two problems: I need to throw away about 1/9 of all the generated points and remove the numpy.array elements that require a lot of RAM:

    x,y = 3*np.random.random((2,size,2))
x = x[
    np.logical_not(np.logical_and(
        np.logical_and(x[:,0] > 1, x[:,0] < 2),
        np.logical_and(x[:,1] > 1, x[:,1] < 2)
        ))
    ]
y = y[
    np.logical_not(np.logical_and(
        np.logical_and(y[:,0] > 1, y[:,0] < 2),
        np.logical_and(y[:,1] > 1, y[:,1] < 2)
        ))
    ]
n = min(x.shape[0], y.shape[0])

UPD: size - , . , ?

UPD: :

def calc_avg_dist(size):
    x,y = 3*np.random.random((2,size,2))
    x = x[
        np.logical_not(np.logical_and(
        np.logical_and(x[:,0] > 1, x[:,0] < 2),
        np.logical_and(x[:,1] > 1, x[:,1] < 2)
        ))
    ]
    y = y[
        np.logical_not(np.logical_and(
        np.logical_and(y[:,0] > 1, y[:,0] < 2),
        np.logical_and(y[:,1] > 1, y[:,1] < 2)
        ))
    ]
    n = min(x.shape[0], y.shape[0])
    diffs = x[:n,:] - y[:n,:]
    return np.sum(np.sqrt(np.einsum('ij,ij->i',diffs,diffs)))/n
+4
3

8 , . :

In [350]: llcorners = np.array([[0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2]])

1x1, . size :

In [351]: corner_indices = np.random.choice(len(llcorners), size=size)

size (x, y) :

In [352]: unit_coords = np.random.random(size=(size, 2))

, :

In [353]: pts = unit_coords + llcorners[corner_indices]

pts (size, 2). size = 2000:

In [363]: plot(pts[:,0], pts[:,1], 'o')
Out[363]: [<matplotlib.lines.Line2D at 0x11000f950>]

plot


...

, . - , . , , ; numpy.random.multinomial .

( , .)

from __future__ import division

import numpy as np


def sample_hollow_lamina(size, outer_width, outer_height, a, b, inner_width, inner_height):
    """
    (a, b) is the lower-left corner of the "hollow".
    """
    llcorners = np.array([[0, 0], [a, 0], [a+inner_width, 0],
                          [0, b], [a+inner_width, b],
                          [0, b+inner_height], [a, b+inner_height], [a+inner_width, b+inner_height]])
    top_height = outer_height - (b + inner_height)
    right_width = outer_width - (a + inner_width)
    widths = np.array([a, inner_width, right_width, a, right_width, a, inner_width, right_width])
    heights = np.array([b, b, b, inner_height, inner_height, top_height, top_height, top_height])
    areas = widths * heights
    shapes = np.column_stack((widths, heights))

    regions = np.random.multinomial(size, areas/areas.sum())
    indices = np.repeat(range(8), regions)
    unit_coords = np.random.random(size=(size, 2))
    pts = unit_coords * shapes[indices] + llcorners[indices]

    return pts

,

In [455]: pts = sample_hollow_lamina(2000, 5, 5, 1, 1, 2, 3)

In [456]: plot(pts[:,0], pts[:,1], 'o', alpha=0.75)
Out[456]: [<matplotlib.lines.Line2D at 0x116da0a50>]

In [457]: grid()

plot

, :

In [465]: pts = sample_hollow_lamina(2000, 3, 3, 0.5, 1.0, 1.5, 0.5)

In [466]: plot(pts[:,0], pts[:,1], 'o', alpha=0.75)
Out[466]: [<matplotlib.lines.Line2D at 0x116e60390>]

In [467]: grid()

plot

+8

- , , , , , .


, "", nsc * nsr , nsc nsr, "" nhc * nhr ( ) nhr nhc, , ohc, ohr

    +------+------+------+------+------+------+------+------+------+ nsr  
    |      |      |      |      |      |      |      |      |      |  
    |      |      |      |      |      |      |      |      |      |  
 ...+------+------+------+------+------+------+------+------+------+ nsr-1  
    |      |      | oooo | oooo |      |      |      |      |      |  
    |      |      | oooo | oooo |      |      |      |      |      |  
    +------+------+------+------+------+------+------+------+------+ ...  
    |      |      | oooo | oooo |      |      |      |      |      |  
    |      |      | oooo | oooo |      |      |      |      |      |  
    +------+------+------+------+------+------+------+------+------+ ...  
    |      |      | oooo | oooo |      |      |      |      |      |  
    |      |      | oooo | oooo |      |      |      |      |      |  
 ohc+------+------+------+------+------+------+------+------+------+ 1  
    |      |      |      |      |      |      |      |      |      |  
    |      |      |      |      |      |      |      |      |      |  
    +------+------+------+------+------+------+------+------+------+ 0  
    0      1      2     ...                         ...   nsc-1   nsc  
                  |             |  
                ohr=2        ohr+nhr

- n ( ) .

, nsq = nsc*nsr -nhc*nhr : , .

, random() [0, 1)

(x, y) = (random(), random())
square = integer(random()*nsq)
(x, y) = (x, y) + (column_of_square_origin(square), row_of_square_origin(square))

, numpy , , for.

, , , .

def origins_of_OK_squares(nsc, nsr, ohc, ohr, nhc, nhr):

    # a set of tuples, each one the origin of a square, for ALL the squares
    s_all = {(x, y) for x in range(nsc) for y in range(nsr)}

    # a set of tuples with the origin of the hole squares
    s_hole = {(x, y) for x in range(ohc,ohc+nhc) for y in range(ohr,ohr+nhr)}

    # the set of the origins of admissible squares is the difference
    s_adm = s_all - s_hole

    # return an array with all the origins --- the order is not important!
    # np.array doesn't like  sets
    return np.array(list(s_adm))

n , (n,2)

    rand_points = np.random.random((n, 2))

n

    placements = np.random.randint(0, nsq, n)

rand_points , placements.

    rand_points += origins_of_OK_squares(nsc, nsr, ohc, ohr, nhc, nhr)[placements]

, numpy , ...

import numpy as np
def samples_wo_hole(n,  nsc, nsr, ohc, ohr, nhc, nhr):
    s_all = {(x, y) for x in range(nsc) for y in range(nsr)}
    s_hole = {(x, y) for x in range(ohc,ohc+nhc) for y in range(ohr,ohr+nhr)}
    rand_points = np.random.random((n, 2))
    placements = np.random.randint(0, nsc*nsr - nhc*nhr, n)
    return rand_points+np.array(list(s_all-s_hole))[placements]
+1

You have 8 equal unit squares in which it is permissible to place points, so draw as many points in the unit square as you want, as in

x = np.random(n, 2)

now it’s enough to randomly select in which of the 8 valid squares each point is placed

sq = np.random.randomint(0, 8, n)

you also need an array of sources

delta = np.array([[0, 0],
                  [1, 0],
                  [2, 0],
                  [0, 1],
                  # no central square
                  [2, 1],
                  [0, 2],
                  [1, 2]
                  [2, 2]])

and finally

x = x + delta[sq]

To generalize the solution, write a function to calculate the array of sources of admissible squares, a possible implementation using sets that are

def origins(n, hole_xor, hole_wd, hole_yor, hole_hg):
    all_origins = {(x,y) for x in range(n) for y in range(n)}
    hole_origins = {(x,y) for x in range(hole_xor, hole_xor+hole_wd)
                          for y in range(hole_yor, hole_yor+hole_hg)}
    return np.array(list(all_origins-hole_origins)))

and use it like this:

delta = origins(12,  4,5,  6,2)
n_squares = len(delta) # or n*n - width*height
target_square = np.random.randomint(0, n_squares, size)
x = x + delta[target_square]
0
source

All Articles