Genera un punto casuale all'interno di un cerchio (uniformemente)


212

Ho bisogno di generare un punto uniformemente casuale all'interno di un cerchio di raggio R .

Mi rendo conto che selezionando semplicemente un angolo uniformemente casuale nell'intervallo [0 ... 2π) e un raggio uniformemente casuale nell'intervallo (0 ... R ), finirei con più punti verso il centro, dato che per due raggi, i punti nel raggio più piccolo saranno più vicini tra loro che per i punti nel raggio più grande.

Ho trovato un post sul blog qui, ma non capisco il suo ragionamento. Suppongo che sia corretto, ma vorrei davvero capire da dove ottiene (2 / R 2 ) × re come si ricava la soluzione finale.


Aggiornamento: 7 anni dopo aver pubblicato questa domanda, non avevo ancora ricevuto una risposta soddisfacente sulla domanda reale riguardante la matematica dietro l'algoritmo radice quadrata. Quindi ho passato una giornata a scrivere una risposta da solo. Link alla mia risposta .


18
Lo svantaggio del campionamento del rifiuto è davvero un grosso problema? Il numero previsto di tentativi richiesti è 4 / π ≈ 1,27 e la probabilità che sia necessario più di k tentativi è (1-π / 4) ^ k. Per k = 20 , questo è ≈ .00000000000004 e per k = 50 è nell'ordine di 10 ^ {- 34}. Puoi prendere quelle probabilità ogni giorno; farai bene.
ShreevatsaR,

3
In realtà, il campionamento del rifiuto fornisce una garanzia per la risoluzione. Le probabilità sono infinitamente basse (per essere precisi, zero) che l'algoritmo non terminerà mai.
Jared Nielsen,

2
A mio avviso, l'importanza dello svantaggio del campionamento del rifiuto è proporzionale alla facilità di utilizzo di un metodo di campionamento che evita il rifiuto. In questo caso, lo svantaggio è importante perché il campionamento senza rifiuto è semplice.
spex,

4
@spex In pratica, la tecnica del rifiuto è più veloce perché evita la necessità di valutazioni della funzione trascendentale.
pjs,

2
(cont) rifiuto: 0,52s Tutti davano mezzi identici e deviazioni standard (a 3 sig. fig.). Come previsto, il campionamento del rifiuto ha fallito il 27% delle volte (4 / pi-1), quindi era necessario il 27% in più di numeri casuali rispetto a btilly ma il 15% in meno di sigfpe. Ciò conferma i commenti fatti da pjs e altri che il campionamento del rifiuto è probabilmente l'approccio migliore, a meno che i randoms non siano molto costosi da generare.
Peter Davidson,

Risposte:


189

Avviciniamoci come avrebbe fatto Archimede.

Come possiamo generare un punto uniformemente in un triangolo ABC, dove | AB | = | BC |? Rendiamolo più semplice estendendo ad un parallelogramma ABCD. È facile generare punti uniformemente in ABCD. Scegliamo uniformemente un punto casuale X su AB e Y su BC e scegliamo Z in modo tale che XBYZ sia un parallelogramma. Per ottenere un punto scelto in modo uniforme nel triangolo originale, pieghiamo tutti i punti che appaiono in ADC di nuovo in ABC lungo AC.

Ora considera un cerchio. Al limite possiamo pensarlo come infiniti triangoli di isocele ABC con B all'origine e A e C sulla circonferenza che svaniscono ravvicinati. Possiamo scegliere uno di questi triangoli semplicemente selezionando un angolo theta. Quindi ora dobbiamo generare una distanza dal centro selezionando un punto nel nastro ABC. Ancora una volta, estendi a ABCD, dove D è ora il doppio del raggio dal centro del cerchio.

Scegliere un punto casuale in ABCD è facile usando il metodo sopra. Scegli un punto casuale su AB. Scegli uniformemente un punto casuale su BC. Vale a dire. scegli una coppia di numeri casuali xey in modo uniforme su [0, R] dando le distanze dal centro. Il nostro triangolo è un sottile nastro, quindi AB e BC sono sostanzialmente paralleli. Quindi il punto Z è semplicemente una distanza x + y dall'origine. Se x + y> R si ripiega verso il basso.

Ecco l'algoritmo completo per R = 1. Spero che tu sia d'accordo è piuttosto semplice. Utilizza trig, ma puoi dare una garanzia su quanto tempo impiegherà e su quante random()chiamate ha bisogno, a differenza del campionamento di rifiuto.

t = 2*pi*random()
u = random()+random()
r = if u>1 then 2-u else u
[r*cos(t), r*sin(t)]

Eccolo in Mathematica.

f[] := Block[{u, t, r},
  u = Random[] + Random[];
  t = Random[] 2 Pi;
  r = If[u > 1, 2 - u, u];
  {r Cos[t], r Sin[t]}
]

ListPlot[Table[f[], {10000}], AspectRatio -> Automatic]

inserisci qui la descrizione dell'immagine


6
@Karelzarath Mi piace l'idea controintuitiva di un triangolo infinitamente sottile che è ancora più largo a un'estremità rispetto all'altra :-) Ottiene la risposta giusta.
sigfpe,

2
@hammar Non sono sicuro che si generalizzi bene a n dimensioni. Ma per 3d puoi usare un altro risultato di Archimede! Usa il teorema "hat-box" per generare un punto sul cilindro (facile!) E quindi mapparlo di nuovo sulla sfera. Questo dà una direzione. Ora random()+random()+random()usalo con qualche piega più complessa (cioè una piega a 6 vie di un parallelepipedo infinitamente sottile fino a un teraedro). Non sono convinto che questo sia un buon metodo.
sigfpe,

2
Ho pensato 1 minuto per capire la differenza tra random () + random () e 2 * random () ... Sono così stupido: /
JiminP

3
@Tharwen Nota come in un cerchio ci siano più punti nel raggio 0,9-1,0 rispetto al raggio 0,0-0,1. random () + random () genera raggi con più probabilità di essere intorno a 1,0 ma compresi nell'intervallo 0,0-2,0. Se ripiegati, hanno maggiori probabilità di essere intorno a 1,0 e sempre nell'intervallo 0,0-1,0. Inoltre, è esattamente la proporzione necessaria nella prima frase di questo commento. Basta dimezzare produce più numeri attorno al segno 0,5 e sarebbe sbagliato.
sigfpe,

2
@Tharwen Prova a utilizzare entrambi gli schemi per generare numeri casuali e vedere cosa ottieni. 2 * random () fornisce numeri distribuiti uniformemente nell'intervallo da 0 a 2. random () + random () ti dà numeri nell'intervallo da 0 a 2 ma (di solito) ci saranno più numeri vicino a 1,0 che vicino a 0,0 o 2,0. È come se lanciare due dadi e sommare abbia più probabilità di dare 7 di qualsiasi altro numero.
sigfpe,

134

Come generare un punto casuale all'interno di un cerchio di raggio R :

r = R * sqrt(random())
theta = random() * 2 * PI

(Supponendo che random()dia un valore tra 0 e 1 uniformemente)

Se vuoi convertirlo in coordinate cartesiane, puoi farlo

x = centerX + r * cos(theta)
y = centerY + r * sin(theta)


Perché sqrt(random())?

Diamo un'occhiata alla matematica che porta a sqrt(random()). Supponiamo per semplicità che stiamo lavorando con il cerchio unitario, cioè R = 1.

La distanza media tra i punti dovrebbe essere la stessa indipendentemente da quanto guardiamo dal centro. Ciò significa, ad esempio, che osservando il perimetro di un cerchio con circonferenza 2 dovremmo trovare il doppio dei punti rispetto al numero di punti sul perimetro di un cerchio con circonferenza 1.


                

Poiché la circonferenza di un cerchio (2π r ) cresce linearmente con r , ne consegue che il numero di punti casuali dovrebbe crescere linearmente con r . In altre parole, la funzione di densità di probabilità desiderata (PDF) cresce in modo lineare. Poiché un PDF dovrebbe avere un'area uguale a 1 e il raggio massimo è 1, abbiamo


                

Quindi sappiamo come dovrebbe apparire la densità desiderata dei nostri valori casuali. Ora: come possiamo generare un valore così casuale quando tutto ciò che abbiamo è un valore casuale uniforme tra 0 e 1?

Usiamo un trucco chiamato campionamento di trasformazioni inverse

  1. Dal PDF, creare la funzione di distribuzione cumulativa (CDF)
  2. Specchia questo lungo y = x
  3. Applicare la funzione risultante su un valore uniforme compreso tra 0 e 1.

Sembra complicato? Permettetemi di inserire una blockquote con un piccolo binario laterale che trasmetta l'intuizione:

Supponiamo di voler generare un punto casuale con la seguente distribuzione:

                

Questo è

  • 1/5 dei punti uniformemente tra 1 e 2, e
  • 4/5 dei punti uniformemente tra 2 e 3.

Il CDF è, come suggerisce il nome, la versione cumulativa del PDF. Intuitivamente: mentre PDF ( x ) descrive il numero di valori casuali in x , CDF ( x ) descrive il numero di valori casuali inferiore a x .

In questo caso il CDF sarebbe simile a:

                

Per vedere come questo sia utile, immagina di sparare proiettili da sinistra a destra ad altezze uniformemente distribuite. Quando i proiettili colpiscono la linea, cadono a terra:

                

Guarda come la densità dei proiettili sul terreno corrisponde alla nostra distribuzione desiderata! Ci siamo quasi!

Il problema è che per questa funzione, l' asse y è l' output e l' asse x è l' input . Possiamo solo "sparare proiettili da terra verso l'alto"! Abbiamo bisogno della funzione inversa!

Questo è il motivo per cui rispecchiamo tutto; x diventa y e y diventa x :

                

Questo CDF viene chiamato -1 . Per ottenere valori in base alla distribuzione desiderata, utilizziamo CDF -1 (random ()).

... quindi, tornando a generare valori di raggio casuali in cui il nostro PDF è uguale a 2 x .

Passaggio 1: creare il CDF:

poiché stiamo lavorando con reals, il CDF è espresso come integrale del PDF.

CDF ( x ) = ∫ 2 x = x 2

Passaggio 2: eseguire il mirroring del CDF lungo y = x :

Matematicamente questo si riduce a scambiare x e y e risolvendo per y :

CDF :      y = x 2
Swap:    x = y 2
Risolvi:    y = √ x
CDF -1 :   y = √ x

Passaggio 3: applicare la funzione risultante a un valore uniforme compreso tra 0 e 1

CDF -1 (random ()) = √random ()

Questo è ciò che abbiamo deciso di derivare :-)


Questo algoritmo può essere utilizzato per generare in modo efficiente punti sull'anello.
Ivan Kovtun,

Sul ring? Come con un raggio fisso? Non sono sicuro di aver compreso la tua domanda, ma se hai un raggio fisso, devi solo randomizzare l'angolo.
aioobe,

2
Ho cercato di usare la parola più semplice "Anello" invece di Annulus - regione delimitata da due cerchi concentrici. In questo caso l'algoritmo di rifiuto diventa non efficace e il primo algoritmo principale è difficile da generalizzare. E anche la custodia ad angolo con un raggio è coperta dall'algoritmo. Generiamo sempre il raggio come sqrt (casuale (min_radius ^ 2, max_radius ^ 2)) anche quando min_radius == max_radius.
Ivan Kovtun,

1
Oh bello! Per essere chiari, quando dici random(min_radius², max_radius²), intendi qualcosa di equivalente a random() * (max_radius² - min_radius²) + min_radius², dove random()restituisce un valore uniforme tra 0 e 1?
aioobe,

sì, è esattamente ciò che intendo: radius = sqrt (random () * (max_radius² - min_radius²) + min_radius²).
Ivan Kovtun,

27

Ecco una soluzione semplice e veloce.

Scegli due numeri casuali nell'intervallo (0, 1), vale a dire ae b. Se b < a, scambiali. Il tuo punto è (b*R*cos(2*pi*a/b), b*R*sin(2*pi*a/b)).

Puoi pensare a questa soluzione come segue. Se prendessi il cerchio, lo tagliassi, poi lo raddrizzassi, otterrai un triangolo rettangolo. Scala quel triangolo verso il basso, e si avrebbe un triangolo da (0, 0)a (1, 0)a (1, 1)e poi di nuovo a (0, 0). Tutte queste trasformazioni cambiano la densità in modo uniforme. Quello che hai fatto è stato uniformemente selezionato un punto casuale nel triangolo e invertito il processo per ottenere un punto nel cerchio.


Questo, per qualche motivo, mi dà una distribuzione molto più uniforme della risposta accettata, anche se avevo bisogno di dividere le coordinate per il raggio, altrimenti è all'interno di un cerchio di R ^ 2
Greg Zaal

3
Grazie, questo è il tuo codice in Java, forse qualcuno lo troverà utile: float random1 = MathUtils.random (); float random2 = MathUtils.random (); float randomXPoint = random2 * raggio MathUtils.cos (MathUtils.PI2 * random1 / random2); float randomYPoint = random2 * raggio MathUtils.sin (MathUtils.PI2 * random1 / random2);
Tony Ceralva,

molto bene! Mi piace l'idea di maggiori probabilità di centralizzare i punti, quindi se non ci scambiamo quando b < apossiamo raggiungere questo obiettivo! ad es. in javascript jsfiddle.net/b0sb5ogL/1
Guilherme,

Penso che la tua soluzione sia sbagliata. Non sta dando risultati uniformi. Dai
un'occhiata a

4
Puoi spiegarci un po 'di più come tagliare il cerchio e raddrizzarlo?
kec,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.