Generación de Variables Aleatorias · Tema 25

Método polar

Variante eficiente sin senos ni cosenos: rechazo en el disco y factor √(−2 ln S / S).

01 · Punto de partida

El giro sin trigonometría

Box-Muller es exacto pero paga un coseno y un seno por par. El método polar (Marsaglia) logra lo mismo sin trigonometría: tira puntos en el cuadrado [−1,1]², se queda con los que caen en el disco S < 1 y los estira con un solo logaritmo.

Es el mismo par gaussiano del Tema 24 por otro camino: un 21 % de los intentos se rechaza, pero cada intento aceptado evita la parte más cara del cálculo.

  • ¿Por qué V₁,V₂ ~ U(−1,1) con S = V₁²+V₂² < 1?
  • ¿De dónde sale el factor √(−2 ln S / S)?
  • ¿Qué tasa de aceptación esperar y cómo auditarla?
  • ¿Cuándo conviene polar sobre Box-Muller?

02 · Definición

Disco uniforme y estiramiento radial

Condicionar (V₁,V₂) a S < 1 da un punto uniforme en el disco: su dirección es el ángulo Θ parejo y su radio al cuadrado es uniforme, el sustituto exacto de U₂ y U₁ de Box-Muller. El factor f convierte ese radio en el R gaussiano.

◉

Rechazo π/4

El disco ocupa π/4 del cuadrado: ~78,5 % de los pares se acepta. La tasa observada es el primer test del generador.

√ln/S

Un log por par

Un logaritmo y una raíz por dos normales, cero trigonometría. El punto (V₁,V₂) ya trae el “coseno y seno” gratis.

×2

Par con sobrante

Como Box-Muller: cada aceptación rinde dos Z independientes; cachear la segunda duplica el rendimiento.

Polar frente a Box-Muller: misma ley, distinto costo.
MétodoUniformes / parTrigonometríaRechazo
Box-Muller2 exactoscos + sin0 %
Polar≈ 2,55 en promedioninguna≈ 21,5 %

03 · Anatomía

Aceptar, estirar y cachear

Bucle de aceptación
Tirar V₁ = 2U−1, V₂ = 2U−1 hasta 0 < S < 1. S = 0 se rechaza (ln 0 indefinido); S ≥ 1 cae fuera del disco. Media de intentos: 4/π ≈ 1,27.
Factor único
f = sqrt(-2 * log(s) / s) una sola vez por par aceptado; Z₀ y Z₁ comparten el factor. No recalcular el log por coordenada.
Sobrante como Box-Muller
Devolver Z₀ y cachear Z₁. Sin caché se tira la mitad del trabajo aceptado, que ya costó rechazos.
Costo real
Más uniformes por par (2,55 vs 2) pero sin senos: en CPUs sin trigonometría rápida el polar gana; con sincos vectorizado la brecha se achica (Tema 39).

Método polar

Rápido y exacto

La opción por defecto cuando la trigonometría duele. Misma exactitud y colas que Box-Muller.

Box-Muller directo

Simple y sin rechazo

Dos uniformes, cero rechazos, costo fijo. Mejor si el costo trigonométrico no es problema.

04 · Dónde rinde el disco

Del cuadrado a la librería

[−1,1]²→S < 1→Z₀,Z₁
  1. 1
    Mismo ruido que Box-Muller.

    N(μ,σ) vía μ + σZ, pares correlacionados y procesos gaussianos.

  2. 2
    CPUs sin trigonometría.

    Microcontroladores y GPUs donde cos/sin son caros: el disco los reemplaza.

  3. 3
    Clase de rechazo.

    Primer rechazo continuo que acepta sin deformar: antesala del Tema 8 aplicado.

  4. 4
    Puente a ziggurat.

    La idea “capas + rechazo barato” escala al método más rápido de todos. (Tema 39)

05 · Representación en Python

Disco, factor y tasa π/4

Implementación con caché más auditoría triple: tasa de aceptación, par incorrelado y banda 68 %.

Python en tu navegador. Subí N a 20000: la tasa debe seguir ≈ 0,785.

import random
import math

random.seed(210)
N = 8000
_spare = []
intentos = 0
aceptados = 0

def polar():
    global intentos, aceptados
    if _spare:
        return _spare.pop()
    while True:
        intentos += 1
        v1 = 2 * random.random() - 1
        v2 = 2 * random.random() - 1
        s = v1 * v1 + v2 * v2
        if 0 < s < 1:
            aceptados += 1
            f = math.sqrt(-2 * math.log(s) / s)
            _spare.append(v2 * f)
            return v1 * f

m = [polar() for _ in range(N)]
media = sum(m) / N
var = sum((x - media) ** 2 for x in m) / N
print("tasa:", round(aceptados / intentos, 3), "vs 0.785")
print("media:", round(media, 3), "var:", round(var, 3))
print("banda |Z|<=1:", round(sum(1 for x in m if abs(x) <= 1) / N, 3), "vs 0.683")

Tasa ≈ 0,785, media ≈ 0, var ≈ 1 y banda ≈ 0,683. Si la tasa da ≈ 1, el rechazo no está filtrando.

06 · Exploración

Laboratorio: el disco que acepta π/4

Cada punto es un intento (V₁,V₂): los celestes caen en el disco y se aceptan; los naranjas caen fuera y se rechazan. La fracción celeste debe rondar π/4 ≈ 0,785 y los Z resultantes, la banda 0,683.

EXPERIMENTO 25

Disco polar

Acepta si 0 < S < 1 · Z = V·√(−2 ln S / S)

Los resultados numéricos aparecen debajo.
Tasa aceptación0,785
Teórica π/40,785
Corr(Z₀,Z₁)0,000
Banda |Z|≤10,683

Con 3000 pares, tasa ≈ 0,785 y banda ≈ 0,683.

Cuadrado [−1,1]² con disco inscripto. Probá N = 500 (tasa ruidosa) y N = 12000 (π/4 clavado).

Preguntas para explorar

  1. Con N = 3000, ¿tasa ≈ 0,785? ¿Banda ≈ 0,683?
  2. ¿Los puntos naranjas se concentran en las esquinas? ¿Por qué ahí?
  3. Bajá a N = 500. ¿La tasa se aleja de π/4? ¿Sesgo o ruido?
Ver respuestas sugeridas
  1. Sí: tasa 0,785 ± 0,015 y banda 0,683 ± 0,02 con N = 3000.
  2. Sí: las esquinas quedan fuera del disco (S > 1). El rechazo vive ahí por geometría.
  3. Ruido: el error típico de la tasa es ≈ √[0,785·0,215/500] ≈ 0,018; solo desvíos mayores delatan bug.

07 · Comprensión

Confusiones frecuentes

«Acepto S ≤ 1 o S = 0 da igual»

S = 0 rompe el factor (división por cero y log de cero). Rechazar 0 < S < 1 estricto.

«V₁,V₂ uniformes en (0,1)»

Deben ser U(−1,1): V = 2U − 1. Con (0,1) el cuadrado no centra el disco y la tasa da otro valor.

«Un log por coordenada»

El factor f se calcula una vez por par aceptado y se comparte. Recalcularlo duplica el costo logarítmico.

«La tasa π/4 no necesita validarse»

Es el test más barato: si da 1,0 no hay rechazo; si da 0,5, el cuadrado o el disco están mal definidos.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: tasa π/4 auditada

N = 10000 (semilla 211): verificá tasa ≈ 0,785, medias ≈ 0 y |corr| < 0,03.

import random
import math

random.seed(211)
N = 10000
intentos = aceptados = 0
pares = []
while len(pares) < N // 2:
    intentos += 1
    v1 = 2 * random.random() - 1
    v2 = 2 * random.random() - 1
    s = v1 * v1 + v2 * v2
    if 0 < s < 1:
        aceptados += 1
        f = math.sqrt(-2 * math.log(s) / s)
        pares.append((v1 * f, v2 * f))
z0 = [a for a, _ in pares]
m0 = sum(z0) / len(z0)
print("tasa:", round(aceptados / intentos, 3), "vs 0.785")
print("media Z0:", round(m0, 3))
Ver solución razonada

Tasa ≈ 0,785 (±0,01 con N = 5000 pares) y media ≈ 0. Si la tasa da ≈ 0,5, generaste V en (0,1) en vez de (−1,1).

Ejercicio 2: de Z a N(μ,σ)

Con μ = 5, σ = 3 y N = 8000, transformá el par polar y verificá media ≈ 5 y banda [2,8] ≈ 0,683.

import random
import math

random.seed(212)
mu, sigma, N = 5.0, 3.0, 8000
m = []
while len(m) < N:
    v1 = 2 * random.random() - 1
    v2 = 2 * random.random() - 1
    s = v1 * v1 + v2 * v2
    if 0 < s < 1:
        f = math.sqrt(-2 * math.log(s) / s)
        m += [mu + sigma * v1 * f, mu + sigma * v2 * f]
media = sum(m) / N
print(round(media, 3), "vs 5.0")
print(round(sum(1 for x in m if abs(x-mu) <= sigma) / N, 3), "vs 0.683")
Ver solución

≈ 5,0 y ≈ 0,68. La ubicación-escala sobre el polar hereda exactitud y colas.

Ejercicio 3: polar vs Box-Muller en uniformes

Contá uniformes por Z de cada método con N = 20000: Box-Muller 1,0 contra polar ≈ 1,27.

Ver una posible respuesta
import random
import math

random.seed(213)
N = 20000
# Box-Muller: 2 U por 2 Z
print("Box-Muller U/Z:", 2 * (N // 2) * 2 / N / 2, "= 1.0")
# Polar: 2 U por intento, 1.27 intentos por par
intentos = aceptados = 0
while aceptados < N // 2:
    intentos += 1
    v1 = 2 * random.random() - 1
    v2 = 2 * random.random() - 1
    if 0 < v1*v1 + v2*v2 < 1:
        aceptados += 1
print("Polar U/Z:", round(2 * intentos / N, 3), "vs 1.27")

Polar gasta ~27 % más uniformes pero cero trigonometría: ahí está el trueque.

09 · Síntesis

Ideas para recordar

  • Polar: V₁,V₂ ~ U(−1,1), acepta 0 < S < 1, f = √(−2 ln S/S).
  • Exacto como Box-Muller, sin senos ni cosenos.
  • Tasa π/4 ≈ 0,785: el test más barato del método.
  • Un log por par, sobrante cacheada, S = 0 siempre rechazado.
  • Trueque: +27 % uniformes a cambio de −100 % trigonometría.

En el próximo tema sumamos exponenciales: la gamma con forma α.