image

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

Прежде чем переходить к кругу, давайте упростим задачу и выберем случайную точку внутри квадрата. Для этого достаточно выбрать случайные координаты $x$ и $y$ в пределах квадрата. В некоторых случаях этот принцип можно применить и к кругам. Например, точка может попадать в круг, но не всегда, поэтому нам нужен дополнительный этап — если выбранная точка находится за границами круга, то мы пробуем снова и снова, пока точка не окажется в круге. Этот процесс является примером выборки с отклонением (rejection sampling) — вместо того, чтобы преобразовать некое распределение, мы просто отклоняем все результаты, находящиеся вне нужного нам интервала.

Способ вполне рабочий, но мне не совсем нравится то, что этот алгоритм иногда приходится исполнять множество раз. Предположим ради простоты, что радиус нашей окружности равен $1$ (но дальнейшие вычисления применимы к кругам любого радиуса). Вероятность того, что выбранная этим алгоритмом точка находится внутри круга, равна площади круга, разделённой на площадь квадрата, или $\pi$, делённая на $4$, что приблизительно равно $0,785$. Это значит, что вероятность выбора алгоритмом неправильной точки $n$ раз равна $1 - pi/4$ в степени $n$, где $n$ — некое положительное число. С увеличением $n$ это значение очень быстро становится очень малым. Вероятность четырёхкратного неправильного срабатывания алгоритма меньше четверти процента. Мы можем вычислить среднее количество необходимых попыток, или ожидаемое значение $n$, которое обратно коэффициенту успешного выбора, то есть $4/pi$, что приблизительно равно $1,273$. То есть в среднем нам достаточно будет сделать только одну попытку. Все эти вычисления дают нам понять, что не стоит беспокоиться о слишком долгом повторении процесса.

Давайте реализуем алгоритм на Python.

def rejection_sampling():
    while True:
        x = random() * 2 - 1
        y = random() * 2 - 1
        if x * x + y * y < 1:
            return x, y

Если я выполню эту функцию красивое количество раз ($3141$), то получу желаемый результат — точки случайно, но равномерно распределены по кругу.


На этом можно было бы и остановиться, но мы можем сделать так, чтобы подходящая точка гарантированно выбиралась с первого раза. Проблема в том, что мы используем неправильную систему координат (декартову), которая ограничивает нас, заставляя выбирать два значения, представляющие собой расстояния на перпендикулярных осях, которые мы называем $x$ и $y$. Гораздо больше нашим целям подойдёт полярная система координат, в которой точки задаются расстоянием от точки начала координат и углом. Например, рассмотрим точку ($2$, $pi/3$). В декартовых координатах это будет точка на пересечении $x = 2$ и $y = pi/3$, но в полярных координатах это будет точка, расположенная в двух единицах от точки начала координат и повёрнутая на угол $pi/3$ радиан.


Достаточно взглянуть на графическое представление этих систем координат, чтобы понять, почему полярная система координат больше подходит для работы с кругами. Итак, всё, что нам нужно сделать — выбрать случайный радиус $r$ в интервале от 0 до 1 и случайный угол $\theta$ (тета) от $0$ до $2 \pi$. Для отрисовки эти координаты необходимо будет преобразовать обратно в декартовы, что можно сделать, умножив $r$ на косинус или синус $\theta$ для получения, соответственно, координат $x$ и $y$


Реализация этого алгоритма на Python содержит случайную переменную $theta$ от $0$ до $2 \pi$ и случайную переменную $r$ от $0$ до $1$, а возвращает $r$, умноженный на косинус $theta$ и $r$, умноженный на синус $theta$.

def random_polar():
    theta = random() * 2 * pi
    r = random()
    return r * cos(theta), r * sin(theta)

Если мы снова выполним этот алгоритм 3141 раз, результаты должны выглядеть похожими на алгоритм выборки с отклонением… но это не так.


Как видите, точки гораздо плотнее располагаются в центре круга. Давайте разберёмся, почему. Мы знаем, что проблема не в генерировании $\theta$, потому что точки равномерно распределены по кругу, поэтому виноват должен быть способ выбора значения $r$. Используемый нами генератор случайных чисел имеет равномерное распределение, то есть вероятность выбора каждого возможного радиуса одинакова. Из этого следует, что каждое кольцо в круге в среднем будет содержать одинаковое количество точек. Возможно, вы уже видите в чём проблема — с увеличением длины окружности кольца количество точек остаётся постоянным, то есть в наружных кольцах с большим радиусом точки имеют меньшую плотность, чем во внутренних.


Мы можем описать процесс выбора радиуса или значения $r$ при помощи функции плотности вероятности (probability density function, PDF) — функции, интеграл которой сообщает нам вероятность попадания непрерывной случайной переменной $x$ в заданный интервал. Сейчас график этой функции является горизонтальной линией от $0$ до $1$, поскольку вероятность выбора каждого значения $r$ одинакова.


Итак, мы знаем, что эта PDF нам не подходит. Давайте выясним, как она должна выглядеть для того, чтобы точки были равномерно распределены в круге. Так как длина окружности линейно зависит от радиуса, для сохранения одинаковой плотности точек их количество должно тоже линейно зависеть от радиуса. Следовательно, наша PDF должна быть линейной функцией, которую мы обозначим как $f(r) = m * r$, где $m$ — некий наклон. Но как нам вычислить значение $m$? Вероятность того, что выбранное значение $r$ будет находиться в интервале от $0$ до $1$, равна 100% или $1$. Следовательно, интеграл или площадь под кривой нашей PDF от $0$ до $1$ тоже должна быть равна $1$. Из этого мы можем вычислить высоту треугольника, а затем вычислить наклон гипотенузы, или составить интеграл. Именно так я и сделаю. При решении интеграла выясняется, что наклон нашей PDF равен $2$, то есть её уравнение имеет вид $f(r) = 2r$.


Любопытно то, что каждая PDF имеет интеграл $1$, то есть этот трюк будет срабатывать всегда. Теперь когда мы знаем, как должно выглядеть распределение, можно реализовать его при помощи одного генератора равномерно распределённых случайных чисел. Необходимо найти преобразование, применив которое к равномерному распределению, можно получить нужное нам линейное распределение. Для начала давайте создадим функцию распределения (cumulative distribution function, CDF), которую я обозначу как $F(r)$. Она представляет собой интеграл PDF, который в нашем случае равен $r^2$. CDF обозначает вероятность того, что случайная переменная $x$ будет меньше или равна аргументу функции.


При помощи этой CDF мы можем воспользоваться стратегией под названием «метод обратного преобразования» (inverse transform sampling): берётся равномерное распределение, например, распределение нашего генератора случайных чисел, и преобразуется так, что хотя наша случайная переменная x не будет иметь равномерного распределения, сами вероятности, отложенные на оси y, будут им обладать. Давайте посмотрим, что произойдёт, если мы возьмём эти вероятности в качестве аргумента функции, по сути получив функцию, обратную нашей CDF. Если мы возьмём какое-то число из равномерно расположенных значений $y$, и посмотрим на соответствующие им значения $x$, то увидим, что получим нужный результат там, где точки более плотно расположены при больших значениях $r$ и более рассеяны при меньших значениях.


Такое понимание уже достаточно ценно, но я покажу вам более строгое доказательство того, что подстановка случайной переменной с равномерным распределением в функцию, обратную нашей CDF, даст нужное нам распределение. Пока мы знаем, что CDF обозначает вероятность того, что случайная переменная $x$ будет меньше или равна аргументу функции $r$, но давайте вернёмся к тому, как выглядит PDF равномерного распределения. Я обозначу случайную переменную с равномерным распределением от $0$ до $1$ как $u$. График — это горизонтальная линия от $0$ до $1$ при $y$ равном $1$. Следовательно, уравнение CDF (интеграл PDF) будет $F_U(r) = r$. Из определения CDF следует, что вероятность того, что $U$ будет меньше или равна $r$, равна $r$. Если мы подставим $0,5$ вместо $r$, то становится ясно, что вероятность того, что значение случайной переменной с равномерным распределением будет меньше или равно $0,5$, равно $0,5$, потому что оно находится посередине между $0$ и $1$. Вернувшись к методу обратного преобразования, мы можем сказать, что $F_X(r)$ равна вероятности того, что $u$ будет меньше или равна $F_X(r)$. Теперь мы можем применить к обоим сторонам неравенства значение, обратное нашей CDF, что даст нам два неравенства, меньших или равных $r$. Следовательно, $x$, случайная переменная с нужным нам распределением, равна обратному значению CDF, применённому к случайной переменной с равномерным распределением, а мы знаем, что функция, обратная CDF, — это $\sqrt{r}$. Давайте реализуем всё это на Python.


def sqrt_dist():
    theta = random * 2 * pi
    r = sqrt(random())
    return r * cos(theta), r * sin(theta)

Единственное отличие между этой и предыдущей функциями заключается в том, что для вычисления $r$ я теперь беру квадратный корень случайного числа. Снова выполнив алгоритм 3141 раз, мы опять увидим, что точки равномерно распределились по кругу.


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

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


Давайте вернёмся к тому, как мы выбирали случайную точку в квадрате — этот способ можно применить и к параллелограммам. Так как мы имеем дело с равнобедренными треугольниками, воспользуемся ромбом, который, как известно, состоит из двух равнобедренных треугольников, соединённых основаниями. Просто выберем случайную точку на двух соседних сторонах, а затем из каждой точки проведём линию, параллельную соседней стороне, и найдём точку их пересечения. Чтобы ограничить интервал только треугольником, мы можем провести диагональ и отражать все точки, попавшие не в ту часть ромба. Давайте обозначим четыре вершины ромба как $a$, $b$, $c$ и $d$, две случайные точки на соседних сторонах $x_1$ и $x_2$, а выбранную точку $t$. Так как треугольники бесконечно тонкие, угол при вершине $c$ будет стремиться к нулю, и при этом высота ромба тоже будет стремиться к нулю. Это значит, что все его стороны, по сути, становятся параллельными. Следовательно, расстояние от центральной точки $c$ до выбранной точки $t$ равна сумме $x_1$ и $x_2$.


Теперь мы при необходимости сможем отразить точку относительно диагонали, которая находится посередине между точками $c$ и $a$.


Всё это уже можно преобразовать в код на Python. Во-первых, $theta$ вычисляется как обычно, но это также можно рассматривать как выбор случайного треугольника, так как они бесконечно тонкие, поэтому каждый треугольник получает собственный угол. Затем сложением двух случайных величин $x_1$ и $x_2$ вычисляется $x$. Если $r$ больше или равен единице, это значит, что точка находится с неправильной стороны ромба и вычисляется значение её отражения вычитанием $r - 2$. Конструкция $return$ остаётся такой же.

def sum_dist():
    theta = random() * 2 * pi
    r = random() + random()
    if r >= 1:
        r = 2 - r;
    return r * cos(theta), r * sin(theta)

Мы снова выполним алгоритм 3141 раз и получим равномерное распределение точек по кругу.

Однако у меня возник вопрос: почему сложение двух случайных величин не равно умножению одной случайной переменной на два? Проще всего рассуждать над этим вопросом на примере игровых костей. Если мы возьмём результат броска одной честной кости и умножим его на два, то увидим, что возвращаются только чётные числа и что кость имеет равномерное распределение. Однако если сложить результаты двух честных костей, то можно увидеть, что вероятность результата $7$ выше, чем вероятность любого другого числа, потому что получить сумму $7$ на двух костях можно шестью разными способами. Сумму же $2$ и $12$ можно получить только одним способом, из-за чего их вероятность становится ниже. Если мы составим график частоты каждого возможного результата, то при приближении суммы к $7$, что является средним $2$ и $12$, частоты сумм увеличиваются.


Если мы представим, что отражаем правую половину графика, результат будет выглядеть очень похожим на желаемую PDF, но на этом я заметил кое-что странное. Оба этих способа вычисляют $r$ как квадратный корень от случайного числа, а отражение суммы двух случайных чисел должно иметь то же распределение с линейной PDF, равной $2r$ и квадратной CDF $r^2$. Поначалу это кажется неправильным: как эти две кажущиеся не связанными друг с другом и совершенно разные функции могут иметь одинаковое распределение? Мы знаем, что распределение способа с квадратным корнем правильное, ведь мы вывели его непосредственно из нужной CDF. Давайте докажем распределение способа с суммами.

К счастью, уже существует распределение Ирвина-Холла, сообщающее нам распределение суммы $n$ равномерно выбранных случайных чисел от 0 до 1. В нашем случае $n = 2$. Вот уравнения для PDF и CDF.


Можно использовать их, но на самом деле это необязательно, потому что существует очень изящный геометрический вывод, хорошо работающий с низкими значениями $n$. Для начала полезно будет отложить две переменные $x_1$ и $x_2$ на осях $x$ и $y$, чтобы лучше их визуализировать. Можно начертить линию, представляющую все возможные способы суммирования $x_1$ и $x_2$ для получения некого числа $t$ от $0$ до $2$. Уравнение прямой будет иметь вид $x_1 + x_2 = t$. Длина этой линии будет пропорциональна PDF, возрастающей в интервале от $0$ до $1$ и снижающейся в интервале от $1$ до $2$. Площадь под этой прямой и в границах этого единичного квадрата представляет собой CDF.


Но нужно помнить о том, что любое значение $t$ от $1$ до $2$ отражается и даёт нам значение $r$. Это значит, что CDF всегда можно представить как площадь треугольника с основанием и высотой $r$, то есть $r^2/2$. Однако поскольку все точки в этом треугольнике по сути удваиваются в результате отражения, эту площадь нужно умножить на $2$ и получить нужную нам CDF ($r^2$).


Но зачем останавливаться на этом? Любая функция, имеющая такое распределение, теоретически должна выбирать значение $r$, генерирующее случайную точку с равномерным распределением внутри круга.

Я покажу вам ещё один способ, но призываю вас попробовать найти собственный, поскольку я перечислил далеко не все варианты. В этом последнем способе выбор значения $r$ выполняется взятием максимума от двух случайных переменных. Я не использую встроенную в Python функцию $max$, поскольку её производительность ниже того, что вы видите в моём коде.

def max_dist():
    theta = random() * 2 * pi
    r = random()
    x = random()
    if x > r:
        r = x
    return r * cos(theta), r * sin(theta)

Мы снова равномерно распределим 3141 точку в круге, но это нужно доказать. Доказательство выполняется схожим с распределением Ирвина-Холла способом — откладыванием величин $x_1$ и $x_2$. Как и ранее, поскольку $r$ равен их максимуму, мы можем разделить квадрат по диагонали. Любая точка, оказавшаяся в красной половине квадрата, возвращает $x_1$, потому что он больше $x_2$. Любая точка, оказавшаяся в синей половине квадрата, аналогично возвращает $x_2$. CDF представлена квадратом с длиной стороны $r$, в котором каждая точка $x_1$ и $x_2$ меньше или равна $r$, а площадь квадрата снова даёт нам CDF, равную $r^2$.


После разбора этих четырёх способов нам осталось протестировать их, чтобы узнать, какой выполняется быстрее всего. Выполнив каждую функцию 3 142 592 раза, мы получили такие значения времени:


По увеличению скорости выполнения: первый способ (rejection_sampling), четвёртый (max_dist), второй (sqrt_dist) и третий (sum_dist)

Значительно быстрее остальных оказалась выборка с отклонением (rejection sampling), и это логично, потому что остальные три способа содержат дополнительные затраты на выполнение операций синуса и косинуса. Значит ли это, что бОльшая часть нашей работы была проделана напрасно и что нам нужно было остановиться раньше? Я так не считаю, потому что если бы не эта задача, мне никогда бы не довелось познакомиться с PDF и CDF, да и с целым множеством интересных геометрических доказательств.

Комментарии (40)


  1. Tiriet
    29.10.2021 09:44
    -3

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


    1. mobi
      29.10.2021 09:50
      +11

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


      1. Tiriet
        29.10.2021 09:56
        +3

        а, точно, недопонял.


  1. FreeNickname
    29.10.2021 09:45
    +6

    Выполнив каждую функцию 3 142 592 раза

    Мне кажется, там должно быть 3 141 592 :) Интересная статья, спасибо!


  1. mobi
    29.10.2021 09:47
    +3

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


    1. osmanpasha
      29.10.2021 11:39
      +4

      Дак они же приведены в самом конце. Правда, судя по всему, на чистом питоне, что несколько снижает ценность такого замера.


      1. iroln
        29.10.2021 21:41
        +1

        Да, в Python для подобных задач надо использовать numpy. Тогда и миллионы точек будут вычисляться за миллисекунды.


        def sqrt_dist(n):
            theta = np.random.rand(n) * 2 * np.pi
            r = np.sqrt(np.random.rand(n))
            return r * np.cos(theta), r * np.sin(theta)
        
        %timeit x, y = sqrt_dist(3141592)
        139 ms ± 9.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)


        1. squaremirrow
          30.10.2021 18:30
          +2

          Можно ускорить еще почти в два раза, просто поставив декоратор numba.njit:


          import numpy as np
          from numba import njit
          
          def sqrt_dist(n):
              theta = np.random.rand(n) * 2 * np.pi
              r = np.sqrt(np.random.rand(n))
              return r * np.cos(theta), r * np.sin(theta)
          
          @njit
          def sqrt_dist_nb(n):
              theta = np.random.rand(n) * 2 * np.pi
              r = np.sqrt(np.random.rand(n))
              return r * np.cos(theta), r * np.sin(theta)
          
           %timeit x, y = sqrt_dist(3141592)
          170 ms ± 1.21 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
          
          %timeit x, y = sqrt_dist_nb(3141592)
          91.4 ms ± 715 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)


  1. Goodwinnew
    29.10.2021 09:48

    Только участок BD в треугольнике на самом деле является частью окружности.

    Т.е. в данном алгоритме существуют области (предельно малые - но они есть), где точек принципиально не будет.

    Я правильно понимаю?


    1. Tiriet
      29.10.2021 09:58
      +2

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


      1. Goodwinnew
        29.10.2021 11:13
        -2

        IMHO - численными методами проблему полностью не устраним. "В пределе" - это математика.

        А тут уменьшая "толщину" треугольников мы увеличиваем покрытие круга - но не 100%, всегда будут существовать зоны, свободные от точек. Да - это будет вида 0,00001% от общей площади - но будет.

        Это как с интегралами. Площадь под кривой 3Х^2 от 1 до 2. Интеграл - легко видеть Х^3 и точное решение 8-1=7

        А численными методами (суммируем площади прямоугольников под кривой) мы получим или 6,999999 или 7,000001 (смотря какие прямоугольники). Уменьшая толщину - мы повышаем точность = но точно 7 мы не получим.

        PS

        причем заданная точность вычислений (численные методы) будет работать в случае интеграла = нам нужна например точность 0,001%. уменьшаем толщину прямоугольников, пока разность между двумя вычислениями не составит требуемую величину.

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


        1. Tiriet
          29.10.2021 11:33
          +3

          мы сначала переходим к пределу и получаем абсолютно точную (не на 0,0001%, а строго!) формулу для получения случайной равномерно-распределенной координаты точки внутри круга, а потом уже по этой точной формуле строим свои точки.


        1. wataru
          29.10.2021 14:08
          +4

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


  1. AVI-crak
    29.10.2021 10:23
    -2

    Заполнить квадрат, обрезать лишнее до круга.


    1. FreeNickname
      29.10.2021 12:16

      Так это тот же самый метод с отсечением (отбрасыванием), только вы шаг отсечения переносите в конец для всех точек. Плюс, сложнее понять, что мы закончили. Допустим, нам надо сгенерировать строго 100 точек внутри круга. Как мы поймём, что мы это сделали? Мы же не знаем, сколько из них в пределах круга, а сколько за пределами, пока не "обрежем".


  1. vesper-bot
    29.10.2021 10:46

    Неплохо, для студента второго курса, изучающего тервер. Для продакшн имеет смысл использовать оба способа — вначале приближение целевой фигуры примитивом, для которого "легко" построить функцию равномерного распределения, затем проверка на попадание точки в фигуру.


    1. Tiriet
      29.10.2021 11:59

      а зачем нужен примитив?

      проверка попадания x*x+y*y < R^2- в худшем случае- три умножения, сложение и сравнение.

      примитив- определение сектора, в котором используем примитив (два сравнения- по xy), определение координаты внутри примитива, отображение на круг и снова проверка попадания с теми же накладными расходами. в чем профит? Если мне надо простой читаемый код, вызываемый сто раз в секунду- то используем равномерный квадрат с отбрасыванием- все просто и понятно, а если надо миллиарды раз в секунду такие точки строить- то я бы тогда больше не за отбрасывание переживал, а за синусы и косинусы- (или любые другие трансцендентные функции)- и снова вернулся бы к отбрасыванию- пересчитать рандом каждые 3 из 10 случаев быстрее и проще, чем два синуса в каждом случае.


      1. vesper-bot
        29.10.2021 13:40
        +1

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


  1. MuKPo6
    29.10.2021 12:46

    Забавно. Ровно эту же задачу я решал, делая для игры Arma 3 скрипт бомбардировки участка в определенном радиусе. Выбирал именно второй метод с полярными координатами. Помню, что заметил странную "равномерность" в распределения бомб, но списал это на "показалось".


    1. zamboga
      30.10.2021 22:55

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


      1. v-oz
        10.11.2021 14:07

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


  1. janatem
    29.10.2021 12:56
    +1

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

    Навскидку вижу такой способ, который, правда, требует вычисления квадратного корня, что гораздо дешевле синуса, но тоже дороговато. Рассмотрим, как распределено значение координаты x. Ее плотность должна быть пропорциональнa длине хорды, которая образуется прямой, заданной уравнением x=const. То есть нужно вычислять отображение sqrt(1-x^2). Осталось найти распределение координаты y при заданном значении x: оно будет распределено равномерно по всей длине хорды. И нужен еще один вызов квадратного корня для нахождения длины хорды.


    1. mobi
      29.10.2021 14:37
      +2

      Хм, у меня попытка обратить интегральную функцию распределения F(x)=2/π∫√(1-x2)dx привела к трансцендентному уравнению y+sin y=C, так что не уверен, что эта идея сработает.


      1. janatem
        29.10.2021 17:07

        Да, действительно, так не получится, я забыл проинтегрировать плотность.


  1. 5oclock
    29.10.2021 13:34
    +1

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

    Я не специалист, поэтому мне не очевидно: а характер распределения (в данном случае — равномерного) после отбрасывания части результатов — сохраняется?


    1. vesper-bot
      29.10.2021 13:41

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


    1. Sayonji
      30.10.2021 04:18

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

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

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


  1. iShrimp
    29.10.2021 19:24
    +2

    Если помнить, что суммирование двух случайных величин эквивалентно свёртке их PDF, то код можно сделать ещё проще:

    def sum_dist():
        theta = random() * pi
        r = random() - random()
        return r * cos(theta), r * sin(theta)

    — так как вычисление random() - random () даёт случайную величину с треугольным распределением, симметричным относительно 0.


    1. wataru
      29.10.2021 19:55

      Но ведь у такой величины будет пик на 0. А надо наоборот — на +-1.


      1. mobi
        29.10.2021 20:02
        +1

        Предлагаете r = 1.0 - abs(random() - random())? (и 2π для theta)


        1. wataru
          29.10.2021 20:08
          +1

          Ну тогда это ничем не лучше/хуже варианта с суммой.


        1. iShrimp
          29.10.2021 20:33

          Правда, у меня ошибка, а ваш вариант верный :)

          Он даже должен быть чуть лучше (быстрее), чем у автора статьи, так как в нём нет ветвления.


  1. pprometey
    30.10.2021 12:28

    Мне на ум сразу пришло одно наиболее востребованное применение - онлайн казино, рулетка)


  1. CImanov
    30.10.2021 16:37

    Есть еще такой вариант, не проверял на скорость но по моему он может быть даже быстрее чем Rejection sampling. Не знаю как назвать этот вариант, она просто изменяет область значений y основываясь на значении x.

    def nosin_dist(n):
    	x = random() * 2 - 1;
    	y = (random() * 2 - 1)*sqrt(1 - x^2);
    	return x, y;


    1. mobi
      30.10.2021 16:43
      +1

      Такой способ не даст равномерное распределение, плотность в окрестности точек (-1,0) и (1,0) будет заметно выше.


      1. CImanov
        30.10.2021 17:58

        Правильно, я пытался найти функцию плотности вероятности, только моих мат. знаний оказалось недостаточно.


        1. wataru
          30.10.2021 17:59

          Вот тут уже пытались. Это совсем не тривиально.


    1. wataru
      30.10.2021 17:58

      Тут та же проблема что и в наивном методе в полярных координатах — не равномерное распределение.


  1. erdbeeranke
    04.11.2021 22:37

    в форме твоего видео для 3Blue1Brown было интересней.


  1. Tarakanator
    08.11.2021 14:53

    Значительно быстрее остальных оказалась выборка с отклонением (rejection sampling)

    Не совсем так. В среднем, да.
    А если считать для систем реального времени, то мы должны закладывать самую долгий вариант расчёта. А он тут бесконечность.