1

What would be the most efficient way, in python, to uniformly sample random two-dimensional numbers from a complicated intersection of surfaces ?

For instance, consider the intersection of many disks or the complementary of the union of many disks.

I know I could simply draw points from a box containing the two-dimensional zone of interest and then check if the desired conditions are satisfied, but I'm wondering if there is a much efficient solution to draw uniformly random points directly from the delimited surface !

Thanks

Toool
  • 361
  • 3
  • 18
  • There are a few unstated assumptions here. Should your samples be uniformly distributed within the area of interest? How do you generate your area? Maybe you can define a function that converts a simpler distribution into your more complex one? – JohanL May 08 '17 at 16:48
  • @JohanL Yes the samples should be uniformly distributed within the area of interest. The area is generated by considering disks of fixed radii centered on given points, the goal is to sample points that do not belong to any of these disks. – Toool May 08 '17 at 17:25

1 Answers1

0

If there are arbitrary circles, there is no way to avoid checking in-or-out

The good way to speed up such check would be building Quadtree, where each node will have fixed (one or zero would be the best) pointers to circle to check against.

Tree search (linear transformation) will get you to the node with one circular check (and here you accept or reject) or no circular check (accept unconditionally).

UPDATE

Some simple implementation. But you might read more about QuadTrees, R-trees and similar trees for spatial data search (see links at the bottom of QuadTree page), because variations of this idea might help you build better algorithms

Some sample code, typed UNTESTED, recursively builds quadtree given list of disks, then at point arrival recursively walks the tree and classify point. Tree walking is O(log2(N)) complexity, and at the end maximum one disk check

def squared(x):
    return x*x

class Disk(object):
    def __init__(self, x, y, r):
        self._x = x
        self._y = y
        self._r = r

    def check(self, x, y):
        return squared(x-self._x) + squared(y-self._y) <= squared(self._r)

class Node(object):
    def __init__(self, llx, lly, urx, ury):
        # nodes, in CCW
        self._NE = None
        self._NW = None
        self._SW = None
        self._SE = None
        # corners
        self._llx = llx
        self._lly = lly
        self._urx = urx
        self._ury = ury

        # dividers
        self._dx = 0.5*(llx + urx)
        self._dy = 0.5*(lly + ury)

        # disk to check
        self._disk = None

    def is_leaf_node(self):
        """
        True if node  has no children
        """
        if self._NE is None:
            if self._NW is None:
                if self._SW is None:
                    if self._SE is None:
                        return True
        return False

    def check_point(self, x, y):
        """
        True if not covered by circle
        """
        if self._disk is None: 
            return True
        return not self._disk.check(x, y)

def check_node(node, disks):
    """
    return sublist of disks which affects the node
    """

    rc = []
    for disk in disks:
        if disk.check(node._llx, node._lly):
            rc.append(disk)
        elif disk.check(node._llx, node._ury):
            rc.append(disk)
        elif disk.check(node._urx, node._lly):
            rc.append(disk)
        elif disk.check(node._urx, node._ury):
            rc.append(disk)

    if len(rc) == 0:
        return None

    return rc

def build(node, disks):
     subdisks = check_node(node, disks)
     if subdisks is None: # empty type of leaf node
         node._disk = None
         return node
     if len(subdisks) == 1: # simple circle leaf node
         node._disk = subdisks
         return node

     node._NE = build(Node(self._dx, self._dy, self._urx, self._ury), disks)
     node._NW = build(Node(self._llx, self._dy, self._dx, self._ury), disks)
     node._SW = build(Node(self._llx, self._lly, self._dx, self._dy), disks)
     node._SE = build(Node(self._dx, self._lly, self._urx, self._dy), disks)

     return node

def check_point(node, x, y):
    if node.is_leaf_node():
        return node.check_point(x, y)        

    if x > node._dx: # SE of NE nodes
        y > node._dy: # NE
            return check_point(node._NE, x, y)

        return check_point(node._SE, x, y)

    # SW or NW
    y > node._dy: # NW
        return check_point(node._NW, x, y)

    return check_point(node._SW, x, y)

# main 
# in the (0,0):(1,1) square
root = build( Node(0.0, 0.0, 1.0, 1.0), disks )

rc = check_point(root, x, y)
Severin Pappadeux
  • 18,636
  • 3
  • 38
  • 64
  • Thank you for your answer ! Quadtrees look interesting indeed. Could you explain me more precisely how I could code a Quadtree in my specific case ? (Considering for instance 4 disks at random) Would it require to partition the space into cells ? I'm note sure of how to implement it in this case. – Toool May 10 '17 at 14:28
  • @Tool please check the update. I already see one bug in the code I wrote - if node is covered completely by one or more circles, might be a problem - please add test for full coverage (all four corners in the circle) and return first disk which does that (would be good enough if more than one covers) – Severin Pappadeux May 10 '17 at 21:20
  • I will test this later for I have to read a little bit about QuadTrees, but from what I understood up until now it seems to be the most efficient way. Accepted, thanks ! – Toool May 12 '17 at 14:45