Números Pseudoaleatorios · Tema 33

Problemas de períodos cortos

Repeticiones que arruinan simulaciones: cuándo el ciclo muerde, cuánto margen pedir y cómo se solapan los streams.

01 · Punto de partida

El déjà vu que invalida todo

Imaginá una simulación de un millón de clientes con un generador de período 65536: a los 65536 el azar empieza de nuevo, idéntico. Tus “clientes” 1 y 65537 son clones, las colas se sincronizan y los promedios mienten con cara seria. Peor: con réplicas paralelas sembradas “al azar”, dos streams pueden solaparse sin que nadie lo note.

Este tema cuantifica el daño: regla de margen, solapamiento de streams y cómo detectar el ciclo antes de que te detecte a vos.

  • ¿Qué pasa exactamente cuando N supera al período?
  • ¿Cuánto margen pedir entre período y consumo?
  • ¿Por qué dos semillas “distintas” pueden solaparse?
  • ¿Re-sembrar a mitad de camino arregla algo?

02 · Definición

Período, consumo y solape

◈

Período p

Valores hasta repetir el estado: m = 16 → p ≤ 16; MT → 2¹⁹⁹³⁷−1. El Tema 10 lo midió; acá se sufre.

⇄

Consumo N

Valores que pide tu simulación: réplicas × pasos × variables. Se estima antes de elegir generador.

▣

Solape

Dos streams que comparten un tramo: réplicas “independientes” calcadas en parte. Nace de sembrar sin esquema (Temas 37–38).

Margen según tu N.
Tu Np mínimo (×1000)Generador apto
10⁶10⁹LCG 2³¹ justo; PCG/MT sobrados
10⁹10¹²PCG64 / MT / Philox; LCG 2³¹ no
10¹²10¹⁵MT / Philox / PCG128; PCG32 no

03 · Daños concretos

Qué rompe la repetición

Pasado p, la secuencia es un calco: medias que no convergen, varianzas que colapsan y tests que “pasan” sobre datos clonados. En paralelo es peor: con p = 2³¹ y 1000 streams de 10⁶ sembrados al azar, el solape esperado ya es apreciable.

Clones en serie
Cliente i y cliente i+p idénticos: la cola simulada se sincroniza en fase y los tiempos de espera se subestiman sistemáticamente.
Solape en paralelo
Semillas al azar ≈ puntos al azar en el ciclo: dos streams de largo L solapan con prob. ≈ 2L/p por par. Con p chico, casi seguro.
Re-sembrar no salva
Re-sembrar con el reloj o con rand() rompe la reproducibilidad y puede achicar el ciclo efectivo (caés en un punto ya visitado). La cura es generador con p grande + streams (Temas 37–38).

Bien dimensionado

p ≫ N + streams

Nunca tocás el ciclo; cada réplica tiene flujo propio garantizado. La simulación escala sin sorpresas.

Mal dimensionado

N ≈ p o semillas al voleo

Clones silenciosos: todo “cierra” pero estima otro experimento. El error no avisa con varianza alta: avisa con réplicas idénticas.

04 · Ejemplos

Tres mordidas del ciclo

Estimar N→Exigir p ≫ N→Streams propios
  1. 1
    Juguete m = 16, N = 100.

    Seis ciclos completos: la “muestra” es el mismo disco 6 veces. Media y χ² mienten con seguridad.

  2. 2
    LCG 2³¹, N = 5·10⁹.

    Dos vueltas al ciclo: la segunda mitad calca la primera. Resultado “estable” y falso.

  3. 3
    100 réplicas con srand(tiempo).

    Dos arranques en el mismo segundo comparten semilla y stream: réplicas gemelas que achican la varianza fingida.

05 · Implementación en Python

Detectar el ciclo y medir el margen

Detectamos el período del juguete con un diccionario estado→índice y auditamos margen p/N para consumos reales.

Python en tu navegador. Cambiá la semilla: el período del LCG de período máximo no cambia; el transitorio sí.

def periodo_lcg(semilla, a=5, c=3, m=16):
    visto = {}
    x = semilla % m
    i = 0
    while x not in visto:
        visto[x] = i
        x = (a * x + c) % m
        i += 1
        if i > m + 5:
            break
    return i - visto[x], visto[x]


p, trans = periodo_lcg(7)
print("periodo:", p, "transitorio:", trans)

Período 16, transitorio 0: ciclo puro desde el arranque (LCG de período máximo).

Margen para tu N

def apto(periodo, n, factor=1000):
    return periodo >= factor * n


for nombre, p in [("juguete m=16", 16), ("LCG 2**31", 2**31 - 1),
                  ("PCG64", 2**64), ("MT", 2**19937 - 1)]:
    print(nombre, "N=1e9:", apto(p, 10**9))

06 · Exploración

Laboratorio: ver el ciclo morder

Tres LCG didácticos con la secuencia dibujada: la línea vertical naranja marca el fin del primer período. Pedí N mayor que p y mirá cómo el calco se superpone exacto.

EXPERIMENTO 33

N contra p

si N ≥ p, repite exacto

Los resultados numéricos aparecen debajo.
Período p—
Distintos en N—
Ciclos en N—
Veredicto—

Con p = 16 y N = 80 hay 5 ciclos exactos.

Pasada la línea naranja todo es calco: mismos valores, mismo orden. Ningún test 1D lo nota si el ciclo es parejo.

Preguntas para explorar

  1. Con m = 16 y N = 16: ¿cuántos distintos? ¿Y con N = 17?
  2. Con m = 1024 y N = 600: ¿hay repetición? ¿Qué margen p/N queda?
  3. Si tu N real fuera 10⁹, ¿qué generador del laboratorio serviría? ¿Cuál de verdad?
Ver respuestas sugeridas
  1. 16 y 16: el 17º repite el 1º. El distinto nº 17 no existe: el ciclo muerde.
  2. No hay repetición, margen 1,7×: pobrísimo (se exige ≥ 100×). Para jugar vale; para publicar, no.
  3. Ninguno del laboratorio: haría falta PCG64/MT/Philox (Tema 17) con p ≥ 10¹².

07 · Comprensión

Confusiones frecuentes

«Período largo = bueno»

RANDU tenía 2²⁹ y era malo en 3D. El período es el tamaño del tanque, no la calidad del combustible.

«Re-sembro cada tanto y estiro el período»

No: re-sembrar reinicia en un punto quizá ya visitado y rompe reproducibilidad. El período del generador no crece por sembrar más.

«Tomar % k achica el período, lo sé y lo acepto»

Peor: puede colapsarlo (los bits bajos de muchos LCG ciclan corto). El mapeo no solo sesga (Tema 22): puede recortar el ciclo.

«Con N = p/2 estoy a salvo»

Margen 2× es temerario: streams, remuestreos y bootstrap multiplican el consumo real. Exigí 100–1000×.

08 · Práctica guiada

Ejercicios con Python

Ejercicio 1: el calco exacto

Generá 40 valores del juguete m = 16 y mostrá que los primeros 16 igualan a los siguientes 16.

x = 7
seq = []
for _ in range(40):
    x = (5 * x + 3) % 16
    seq.append(x)
print(seq[:16] == seq[16:32])
Ver solución razonada

True: calco perfecto. Toda estadística sobre N = 40 es la del ciclo de 16 contada 2,5 veces.

Ejercicio 2: dimensionar de verdad

Tu simulación pide 200 réplicas × 50000 pasos × 3 variables. ¿Sirve un LCG 2³¹? ¿Y PCG64?

n = 200 * 50000 * 3
for nombre, p in [("LCG 2**31", 2**31 - 1), ("PCG64", 2**64)]:
    print(nombre, "N:", n, "margen:", round(p / n, 1))
Ver solución

N = 3·10⁷. LCG: margen ≈ 71× (corto, < 100×). PCG64: margen ≈ 6·10¹¹ (sobrado). Decisión: PCG o superior.

Ejercicio 3: gemelas por reloj

Simulá dos réplicas sembradas con el mismo segundo y mostrá que salen idénticas.

Ver una posible respuesta
import random

def replica(semilla):
    rng = random.Random(semilla)
    return [rng.random() for _ in range(5)]


print(replica(1718440000) == replica(1718440000))

True: mismo segundo, mismo stream, varianza entre réplicas = 0. Por eso se siembra por índice (Temas 37–38).

09 · Síntesis

Ideas para recordar

  • N ≥ p repite exacto; exigir p ≥ 100–1000×N estimado antes de simular.
  • Solape en paralelo: semillas al azar ≈ puntos al azar en el ciclo.
  • Re-sembrar no estira p y rompe reproducibilidad.
  • Período largo ≠ calidad (RANDU); % puede colapsar el ciclo.
  • Cura: generador grande + streams por índice (Temas 17, 37–38).

En el próximo tema, otro sesgo silencioso: sesgos en la generación y sus causas.