01 · Punto de partida
Por qué mirar atrás antes de programar
En el Tema 10 mediste períodos en juguetes. La historia explica por qué esos juguetes existen y por qué los reales son como son: cada generador famoso nació de una necesidad (velocidad, período, memoria) y varios murieron por un defecto que hoy detectás en minutos.
Conocer dos fracasos —el middle-square que colapsa a cero y el RANDU que alineaba puntos en planos— te vacuna contra improvisar parámetros «que parecen andar».
- ¿Cómo se generaba azar antes de las computadoras?
- ¿Por qué el método de von Neumann colapsa?
- ¿Qué fue RANDU y por qué es la advertencia del curso?
- ¿Qué cambió con Mersenne Twister y los modernos?
02 · Línea de tiempo
De la tabla impresa al PCG en 70 años
1927–1950: tablas y dados
Tippett (1927) y RAND (1947) publican tablas de dígitos; ENIAC tira dados reales. Lento, irrepetible, inmanejable para miles de valores.
1946–1949: middle-square y Lehmer
Von Neumann propone cuadrados medios (ingenioso, pero colapsa a 0). Lehmer (1948) crea el congruencial: rápido y analizable. Nace el LCG.
1960–hoy: RANDU, tests y modernos
RANDU (60s) muestra el peligro de elegir mal a. Marsaglia (Diehard), L'Ecuyer (TestU01), MT (1997), xorshift (2003), PCG (2014).
| Año | Hito | Lección |
|---|---|---|
| 1946 | Middle-square de von Neumann | Simple no es sano: converge a 0 |
| 1948 | LCG de Lehmer | Rápido y con teoría de período |
| 1960s | RANDU a=65539, m=2³¹ | Triplas caen en 15 planos: desastre espectral |
| 1995–2007 | Diehard / TestU01 | Baterías que ningún serio evita |
| 1997 / 2014 | MT / PCG, Philox | Período enorme + validación + velocidad |
03 · Fracasos que enseñan
Dos autopsias obligatorias
- Middle-square: muerte por cero
- Tomá 4 dígitos, elevalos al cuadrado (8 dígitos), quedate con los 4 del medio. Si alguna vez sale
0000, queda en 0 para siempre; y hay muchas semillas que drenan a ciclos cortos. Von Neumann lo sabía: «quien use aritmética para azar peca», pero era lo único rápido en 1946. - RANDU: el desastre elegante
- IBM eligió
a = 65539 = 2¹⁶+3por velocidad (shifts y sumas). En 1D parecía bien; en 3D las triplas(u(n),u(n+1),u(n+2))caían en 15 planos paralelos. Simulaciones enteras de los 60–70 heredaron ese sesgo. Marsaglia lo inmortalizó: «los números caen en los planos». - Lección común
- Ninguno se veía mal en media e histograma 1D. Hizo falta mirar pares, triplas y teoría espectral. Por eso los Temas 30–32 existen.
Error histórico
Elegir por velocidad
RANDU era rapidísimo y «pasaba» lo que se testeaba entonces. El costo lo pagaron miles de resultados.
Práctica actual
Elegir validado
Parámetros con teoría + batería (TestU01 BigCrush, PractRand) + código auditado. No se inventa un LCG casero para producir.
04 · Mapa
Qué quedó en pie
- 1LCG bien parametrizado.
Sigue vivo en didáctica y sistemas chicos: simple, rapidísimo, período conocido. Lo dominarás en los Temas 12–14.
- 2Combinados de L'Ecuyer.
Mezclar dos LCG alarga el período y rompe estructuras. Puente al Tema 15.
- 3MT, xorshift, PCG, Philox.
Períodos astronómicos, excelente uniformidad multidimensional y velocidad. El estándar para simular hoy (Tema 17).
05 · Autopsia en Python
Ver morir al middle-square
Con 4 dígitos lo ves colapsar en pocas iteraciones desde muchísimas semillas. Ningún LCG decente hace esto.
Python en tu navegador. Probá semillas 1234, 6789 y 1000: alguna muere en 0 rapidísimo.
def middle_square(semilla, n=12):
x = semilla
sal = []
for _ in range(n):
x = (x * x) % 100_000_000
x = (x // 100) % 10_000
sal.append(x)
return sal
print(middle_square(1234))
print(middle_square(1000))
Verás ceros o ciclos de 1–2 valores: el generador «se traga» a sí mismo. Von Neumann lo usó por necesidad, no por bueno.
El LCG no muere así
def lcg(semilla, a=5, c=3, m=16, n=12):
x = semilla
sal = []
for _ in range(n):
x = (a * x + c) % m
sal.append(x)
return sal
print(lcg(7))
06 · Exploración
Laboratorio: museo de generadores
Elegí el generador y la semilla. El canvas muestra u(n) en orden (middle-square normalizado a /10000, LCGs a /m). Observá cuál se apaga y cuáles siguen vivos.
El que colapsa y los que resisten
middle-square vs. LCG
Probá semilla 1000 en middle-square: muere pronto.
Línea que se aplana en cero = generador muerto. Líneas que siguen oscilando = ciclos vivos.
Preguntas para explorar
- Con middle-square y semilla 1000, ¿en cuántos pasos llega a 0? ¿Y con 1234?
- Con LCG bueno, ¿colapsa alguna semilla entre 1 y 15? ¿Y el malo cuántos distintos deja?
- Si en 1946 solo tenías middle-square, ¿qué precaución mínima tomarías?
Ver respuestas sugeridas
- 1000 muere en 1–2 pasos (1000² = 1000000 → medio 0000); 1234 agoniza en una decena con ciclos cortos.
- El bueno no colapsa: recorre el anillo. El malo deja ≤ 4 distintos: vivo pero inútil.
- Monitorear colapso, reiniciar con semilla fresca y jamás creerle a ciegas: la lección que llevó a Lehmer.
07 · Comprensión
Confusiones frecuentes
«Viejo = obsoleto, nuevo = bueno»
Un LCG bien elegido sigue siendo válido para usos simples; un «nuevo» sin tests puede ser peor que RANDU. Valen teoría + batería, no la fecha.
«Middle-square con buena semilla sirve»
No: el espacio está lleno de trampas que drenan a 0 o a ciclos de 1. Ninguna semilla lo hace confiable para N grande.
«RANDU era bueno porque lo usaba IBM»
La autoridad no certifica azar. RANDU pasó lo poco que se testeaba y falló lo que no se miraba (3D). Hoy no pasaría ni el primer filtro.
«La historia no afecta mi código»
Cada default que usás (random, NumPy) es una decisión histórica auditada. Entenderla te dice cuándo quedarte y cuándo migrar (Tema 17).
08 · Práctica guiada
Ejercicios con Python
Ejercicio 1: cazar el cero
Barre semillas 1–20 en middle-square de 4 dígitos y contá cuántas mueren en 0 antes de 20 pasos.
def muere(semilla, pasos=20):
x = semilla
for _ in range(pasos):
x = (x * x) % 100_000_000
x = (x // 100) % 10_000
if x == 0:
return True
return False
print(sum(muere(s) for s in range(1, 21)), "de 20 mueren")
Ver solución razonada
Una fracción alta muere o cae en ciclos de 1–2: con 20 semillas ya ves el patrón. Ningún generador serio pierde así.
Ejercicio 2: ordenar la historia
Sin mirar: ordená middle-square, LCG de Lehmer, RANDU, Diehard/TestU01, Mersenne Twister, PCG. Luego verificá con la tabla del Tema.
orden = ["middle-square", "LCG Lehmer", "RANDU", "Diehard/TestU01", "MT", "PCG"]
print(" → ".join(orden))
Ver solución
1946 → 1948 → 60s → 1995/2007 → 1997 → 2014. Si dudas entre MT y TestU01, recordá: primero el problema (RANDU), luego el detector (baterías), luego el estándar (MT).
Ejercicio 3: tu criterio de adopción
Escribí una función que decida si adoptarías un generador «novedoso» según período declarado y tests pasados.
Ver una posible respuesta
def adoptar(periodo, tests_ok, teoria_ok):
return periodo > 2**60 and tests_ok and teoria_ok
print(adoptar(2**32, True, False))
print(adoptar(2**128, True, True))
El primero se rechaza por falta de teoría aunque pase tests chicos; el segundo pasa el filtro inicial. Es el checklist mínimo antes de usar algo en producción.
09 · Síntesis
Ideas para recordar
- Tablas → middle-square (colapsa) → LCG (rápido, analizable) → RANDU (desastre 3D) → baterías → MT/modernos.
- Middle-square muere en 0; RANDU vive pero en 15 planos.
- 1D no basta: la historia obligó a mirar pares, triplas y espectros.
- No se adopta sin período demostrado + batería + teoría.
- Tu default actual es historia auditada: úsalo con criterio, no por inercia.
En el próximo tema abrimos el motor que ganó: los generadores congruenciales lineales, su fórmula y su uso correcto.