Выборка из заданного региона - PullRequest
2 голосов
/ 30 июня 2019

Я изучаю способ выборки из следующей области по двумерной координате (синяя область):

enter image description here

Конечно, базовая линия заключается в том, что я могу выбрать случайное число (x,y), а затем проверить, перекрывается ли оно с меньшим или вне большого поля. Но это просто тратит слишком много вычислительных ресурсов после некоторой быстрой пробной версии.

Любые предложения или советы будут оценены, спасибо.

Ответы [ 3 ]

2 голосов
/ 30 июня 2019

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

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

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

Прежде всего, на чертеже выглядит, что прямоугольники всегда центрированы в начале координат (оба). Если это допустимое предположение, части следующего решения могут быть упрощены.

Тогда не совсем понятно, как следует использовать предложенное вами "базовое" решение. Вы предложили сгенерировать точки (x, y), и для каждой вы проверяете, содержится ли она во внутреннем прямоугольнике. Если он содержится во внутреннем прямоугольнике, то он отбрасывается.

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

Это не может быть решено детерминистически. Или более формально: вы не можете предоставить полностью правильную реализацию этого. Генератор случайных чисел может всегда генерировать точки, которые находятся во внутреннем прямоугольнике и, следовательно, отбрасываться. Конечно, на практике это не удастся, но вы не можете это доказать, и в этом суть.

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


Однако следующее является решением, которое не страдает от вышеупомянутых проблем. Это обходится дорого: это не особенно эффективное решение (хотя, как упоминалось выше, «относительная эффективность» по сравнению с базовым решением зависит от размеров прямоугольников и схемы использования).

Предполагая, что координаты угловых точек даны как на этом рисунке:

(x0,y0)                        (x3,y3)
   O------------------------------O
   |                              |
   |  (ix0,iy0)        (ix3,iy3)  |
   |      O----------------O      |
   |      |                |      |
   |      |                |      |
   |      |                |      |
   |      |                |      |
   |      |                |      |
   |      O----------------O      |
   |  (ix1,iy1)        (ix2,iy2)  |
   |                              |
   O------------------------------O
(x1,y1)                        (x2,y2)

(обратите внимание, что координаты произвольны, а прямоугольники не обязательно центрированы в начале координат)

Исходя из этого, вы можете вычислить регионы, которые могут содержать точки:

   O------O----------------O------O
   |      |                |      |
   |  R0  |       R1       |  R2  |
   O------O----------------O------|
   |      |                |      |
   |      |                |      |
   |  R2  |                |  R4  |
   |      |                |      |
   |      |                |      |
   O------O----------------O------O
   |  R5  |       R6       |  R7  |
   |      |                |      |
   O------O----------------O------O

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

Предостережение - это выбор региона: регион следует выбирать с вероятностью, соответствующей области региона, относительно общей площади всех регионов. Прагматично, вы можете вычислить общую площадь всех регионов (как outer.w*outer.h-inner.w*inner.h), а затем вычислить совокупное распределение вероятностей для точки, заканчивающейся в одной из областей R0...R7. Из этих кумулятивных распределений вы можете отобразить случайное значение между 0.0 и 1.0 в соответствующий регион.

Преимущества этого подхода:

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

Вот пример, показывающий результат, перетаскивая ползунок, чтобы сгенерировать 1 ... 2000 точек:

RegionNoise

Он был создан с помощью следующего MCVE. Он реализован в Java, но если у вас есть некоторые структуры, такие как Point и Rectangle, перенести это на другие языки будет довольно тривиально:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;

public class RegionNoiseTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());

        RegionNoiseTestPanel panel = 
            new RegionNoiseTestPanel();
        f.getContentPane().add(panel, BorderLayout.CENTER);

        JSlider nSlider = new JSlider(1, 2000, 1);
        nSlider.addChangeListener(e -> 
        {
            panel.generatePoints(nSlider.getValue());
        });
        nSlider.setValue(100);
        f.getContentPane().add(nSlider, BorderLayout.SOUTH);

        f.setSize(500,450);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

}

class RegionNoiseTestPanel extends JPanel
{
    private final Rectangle2D outer;
    private final Rectangle2D inner;
    private List<Point2D> points;


    RegionNoiseTestPanel()
    {
        outer = new Rectangle2D.Double(50, 50, 400, 300);
        inner = new Rectangle2D.Double(90, 100, 300, 200);
    }

    public void generatePoints(int n)
    {
        this.points = createPoints(n, outer, inner, new Random(0));
        repaint();
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(new Color(220, 220, 220));
        g.fill(outer);
        g.setColor(new Color(160, 160, 160));
        g.fill(inner);

        if (points != null)
        {
            g.setColor(Color.BLUE);
            for (Point2D p : points)
            {
                double r = 2;
                double x = p.getX();
                double y = p.getY();
                g.fill(new Ellipse2D.Double(x - r, y - r, r + r, r + r));
            }
        }


    }

    private static List<Point2D> createPoints(
        int n, Rectangle2D outer, Rectangle2D inner, Random random)
    {
        List<Rectangle2D> regions = computeRegions(outer, inner);
        double cumulativeRegionAreas[] = new double[8];
        double outerArea = outer.getWidth() * outer.getHeight();
        double innerArea = inner.getWidth() * inner.getHeight();
        double relevantArea = outerArea - innerArea;
        double areaSum = 0;
        for (int i = 0; i < regions.size(); i++)
        {
            Rectangle2D region = regions.get(i);
            double area = region.getWidth() * region.getHeight();
            areaSum += area;
            cumulativeRegionAreas[i] = areaSum / relevantArea;
        }

        List<Point2D> points = new ArrayList<Point2D>();
        for (int i=0; i<n; i++)
        {
            points.add(createPoint(
                regions, cumulativeRegionAreas, random));
        }
        return points;
    }

    private static List<Rectangle2D> computeRegions(
        Rectangle2D outer, Rectangle2D inner)
    {
        List<Rectangle2D> regions = new ArrayList<Rectangle2D>();
        for (int r = 0; r < 8; r++)
        {
            regions.add(createRegion(outer, inner, r));
        }
        return regions;
    }

    private static Point2D createPoint(
        List<Rectangle2D> regions, 
        double normalizedCumulativeRegionAreas[], 
        Random random)
    {
        double alpha = random.nextDouble();
        int index = Arrays.binarySearch(normalizedCumulativeRegionAreas, alpha);
        if (index < 0)
        {
            index = -(index + 1);
        }
        Rectangle2D region = regions.get(index);
        double minX = region.getMinX();
        double minY = region.getMinY();
        double maxX = region.getMaxX();
        double maxY = region.getMaxY();
        double x = minX + random.nextDouble() * (maxX - minX);
        double y = minY + random.nextDouble() * (maxY - minY);
        return new Point2D.Double(x, y);
    }

    private static Rectangle2D createRegion(
        Rectangle2D outer, Rectangle2D inner, int region)
    {
        double minX = 0;
        double minY = 0;
        double maxX = 0;
        double maxY = 0;
        switch (region) 
        {
            case 0:
                minX = outer.getMinX();
                minY = outer.getMinY();
                maxX = inner.getMinX();
                maxY = inner.getMinY();
                break;

            case 1:
                minX = inner.getMinX();
                minY = outer.getMinY();
                maxX = inner.getMaxX();
                maxY = inner.getMinY();
                break;

            case 2:
                minX = inner.getMaxX();
                minY = outer.getMinY();
                maxX = outer.getMaxX();
                maxY = inner.getMinY();
                break;

            case 3:
                minX = outer.getMinX();
                minY = inner.getMinY();
                maxX = inner.getMinX();
                maxY = inner.getMaxY();
                break;

            case 4:
                minX = inner.getMaxX();
                minY = inner.getMinY();
                maxX = outer.getMaxX();
                maxY = inner.getMaxY();
                break;

            case 5:
                minX = outer.getMinX();
                minY = inner.getMaxY();
                maxX = inner.getMinX();
                maxY = outer.getMaxY();
                break;

            case 6:
                minX = inner.getMinX();
                minY = inner.getMaxY();
                maxX = inner.getMaxX();
                maxY = outer.getMaxY();
                break;

            case 7:
                minX = inner.getMaxX();
                minY = inner.getMaxY();
                maxX = outer.getMaxX();
                maxY = outer.getMaxY();
                break;
        }
        return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
    }

}

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

1 голос
/ 01 июля 2019

Вот решение, в котором предполагается, что синяя область симметрична и центрирована в начале координат, поэтому есть 4 параметра (x1, x2, y1, y2). Представьте, что внутри синей области находится еще одна область с такими же пропорциями, но уменьшенная так, что внешняя граница этой другой области точно совпадает с внутренней границей синей области. Если мы случайным образом генерируем точку, и она находится внутри этой внутренней области, мы можем сопоставить ее с точкой в ​​синей области, масштабируя x и y на x2 / x1 и y2 / y1 соответственно. Теперь представьте другой регион внутри этого, а другой внутри него, до бесконечности. Любую точку (кроме начальной точки) можно затем сопоставить с точкой в ​​синей области, просто увеличив ее в нужное количество раз:

// generate a random point:
double x = 0.0, y = 0.0;
while(x == 0.0 && y == 0.0) // exclude the origin
{
    x = random.NextDouble() * x2;
    y = random.NextDouble() * y2;
}

// map to the blue region
while(x < x1 && y < y1)
{
    x *= (x2 / x1);
    y *= (y2 / y1);
}

// randomly choose a quadrant:
int r = random.Next(0, 4);
if((r & 1) != 0)
    x = -x;
if((r & 2) != 0)
    y = -y;

Однако это не так здорово из-за второго цикла while (первый цикл while практически никогда не будет запускаться более одного раза). Цикл можно устранить с помощью логарифмов:

// map to the blue region
if(x < x1 && y < y1)
{
    double xpower = Math.Ceiling((Math.Log(x1) - Math.Log(x)) / Math.Log(x2/x1));
    double ypower = Math.Ceiling((Math.Log(y1) - Math.Log(y)) / Math.Log(y2/y1));
    double power = Math.Min(xpower, ypower);
    x *= Math.Pow(x2/x1, power);
    y *= Math.Pow(y2/y1, power);
}
1 голос
/ 30 июня 2019

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

def sample():
    sample point x_base from [-1, 1] and calculate x = sign(x_base)*x1 + x_base*(x2-x1)
    sample point y_base from [-1, 1] and calculate y = sign(y_base)*y1 + y_base*(y2-y1)
    if (x,y) == (0,0) then:
        # recursively sample again in the rare case of sampling 0 for both dimensions
        return sample()
    else:
        return (x,y)

EDIT: Это решение не будет правильно отбирать образцы из всей синей области, как указано Marco13 . Проверьте его ответ для лучшего подхода.

...